From 5ee2663e8e539ac665719221547b681d9b9d2bb9 Mon Sep 17 00:00:00 2001 From: Victor Castell Date: Sat, 4 Jan 2020 00:40:15 +0100 Subject: [PATCH 1/4] refactor: Allow pro appliers This refactor includes: * Remove dependency of the agent in store and reduce usage in Job * Upgrade gin * Add helper methods * Move directory creation to the Store instantiation --- dkron/agent.go | 29 +++++++++++++++-------------- dkron/api.go | 6 +++++- dkron/fsm.go | 20 ++++++++++++++++++-- dkron/job.go | 9 ++------- dkron/job_test.go | 17 ++++++++--------- dkron/leader.go | 2 +- dkron/options.go | 7 +++++++ dkron/queries_test.go | 7 +++++-- dkron/scheduler.go | 9 +++++---- dkron/scheduler_test.go | 8 +++++--- dkron/store.go | 37 +++++++++++++++++++++++-------------- dkron/store_test.go | 4 ++-- go.mod | 6 +----- go.sum | 26 ++++++++++++++++++++++++++ 14 files changed, 123 insertions(+), 64 deletions(-) diff --git a/dkron/agent.go b/dkron/agent.go index 5cb1ef33a..05c94b432 100644 --- a/dkron/agent.go +++ b/dkron/agent.go @@ -70,6 +70,7 @@ type Agent struct { // Pro features GlobalLock bool MemberEventHandler func(serf.Event) + ProAppliers LogAppliers serf *serf.Serf config *Config @@ -295,7 +296,7 @@ func (a *Agent) setupRaft() error { if err != nil { return fmt.Errorf("recovery failed to parse peers.json: %v", err) } - tmpFsm := newFSM(nil) + tmpFsm := newFSM(nil, nil) if err := raft.RecoverCluster(config, tmpFsm, logStore, stableStore, snapshots, transport, configuration); err != nil { return fmt.Errorf("recovery failed: %v", err) @@ -331,13 +332,14 @@ func (a *Agent) setupRaft() error { // Instantiate the Raft systems. The second parameter is a finite state machine // which stores the actual kv pairs and is operated upon through Apply(). - fsm := newFSM(a.Store) + fsm := newFSM(a.Store, a.ProAppliers) rft, err := raft.NewRaft(config, fsm, logStore, stableStore, snapshots, transport) if err != nil { return fmt.Errorf("new raft: %s", err) } a.leaderCh = rft.LeaderCh() a.raft = rft + return nil } @@ -451,18 +453,7 @@ func (a *Agent) StartServer() { } if a.Store == nil { - dirExists, err := exists(a.config.DataDir) - if err != nil { - log.WithError(err).WithField("dir", a.config.DataDir).Fatal("Invalid Dir") - } - if !dirExists { - // Try to create the directory - err := os.Mkdir(a.config.DataDir, 0700) - if err != nil { - log.WithError(err).WithField("dir", a.config.DataDir).Fatal("Error Creating Dir") - } - } - s, err := NewStore(a, filepath.Join(a.config.DataDir, a.config.NodeName)) + s, err := NewStore(filepath.Join(a.config.DataDir, a.config.NodeName)) if err != nil { log.WithError(err).Fatal("dkron: Error initializing store") } @@ -572,6 +563,11 @@ func (a *Agent) LocalMember() serf.Member { return a.serf.LocalMember() } +// Leader is used to return the Raft leader +func (a *Agent) Leader() raft.ServerAddress { + return a.raft.Leader() +} + // Servers returns a list of known server func (a *Agent) Servers() (members []*ServerParts) { for _, member := range a.serf.Members() { @@ -880,3 +876,8 @@ func (a *Agent) applySetJob(job *proto.Job) error { return nil } + +// RaftApply applies a command to the Raft log +func (a *Agent) RaftApply(cmd []byte) raft.ApplyFuture { + return a.raft.Apply(cmd, raftTimeout) +} diff --git a/dkron/api.go b/dkron/api.go index b26b1a0f3..4359a7b3d 100644 --- a/dkron/api.go +++ b/dkron/api.go @@ -117,7 +117,11 @@ func (h *HTTPTransport) indexHandler(c *gin.Context) { func (h *HTTPTransport) jobsHandler(c *gin.Context) { metadata := c.QueryMap("metadata") - jobs, err := h.agent.Store.GetJobs(&JobOptions{Metadata: metadata}) + jobs, err := h.agent.Store.GetJobs( + &JobOptions{ + Metadata: metadata, + }, + ) if err != nil { log.WithError(err).Error("api: Unable to get jobs, store not reachable.") return diff --git a/dkron/fsm.go b/dkron/fsm.go index 7237dfccb..44c60378e 100644 --- a/dkron/fsm.go +++ b/dkron/fsm.go @@ -26,15 +26,26 @@ const ( ExecutionDoneType ) +// LogApplier is the definition of a function that can apply a Raft log +type LogApplier func(buf []byte, index uint64) interface{} + +// LogAppliers is a mapping of the Raft MessageType to the appropriate log +// applier +type LogAppliers map[MessageType]LogApplier + type dkronFSM struct { store Storage mu sync.Mutex + + // proAppliers holds the set of pro only LogAppliers + proAppliers LogAppliers } // NewFSM is used to construct a new FSM with a blank state -func newFSM(store Storage) *dkronFSM { +func newFSM(store Storage, logAppliers LogAppliers) *dkronFSM { return &dkronFSM{ - store: store, + store: store, + proAppliers: logAppliers, } } @@ -56,6 +67,11 @@ func (d *dkronFSM) Apply(l *raft.Log) interface{} { return d.applySetExecution(buf[1:]) } + // Check enterprise only message types. + if applier, ok := d.proAppliers[msgType]; ok { + return applier(buf[1:], l.Index) + } + return nil } diff --git a/dkron/job.go b/dkron/job.go index 410253917..09916c9e6 100644 --- a/dkron/job.go +++ b/dkron/job.go @@ -279,12 +279,7 @@ func (j *Job) GetStatus() string { } // GetParent returns the parent job of a job -func (j *Job) GetParent() (*Job, error) { - // Maybe we are testing - if j.Agent == nil { - return nil, ErrNoAgent - } - +func (j *Job) GetParent(store *Store) (*Job, error) { if j.Name == j.ParentJob { return nil, ErrSameParent } @@ -293,7 +288,7 @@ func (j *Job) GetParent() (*Job, error) { return nil, ErrNoParent } - parentJob, err := j.Agent.Store.GetJob(j.ParentJob, nil) + parentJob, err := store.GetJob(j.ParentJob, nil) if err != nil { if err == badger.ErrKeyNotFound { return nil, ErrParentJobNotFound diff --git a/dkron/job_test.go b/dkron/job_test.go index ee767fe2a..6e02f0f49 100644 --- a/dkron/job_test.go +++ b/dkron/job_test.go @@ -15,11 +15,9 @@ func TestJobGetParent(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(dir) - a := &Agent{} - s, err := NewStore(a, dir) + s, err := NewStore(dir) defer s.Shutdown() require.NoError(t, err) - a.Store = s parentTestJob := &Job{ Name: "parent_test", @@ -42,11 +40,11 @@ func TestJobGetParent(t *testing.T) { err = s.SetJob(dependentTestJob, true) assert.NoError(t, err) - parentTestJob, err = dependentTestJob.GetParent() + parentTestJob, err = dependentTestJob.GetParent(s) assert.NoError(t, err) assert.Equal(t, []string{dependentTestJob.Name}, parentTestJob.DependentJobs) - ptj, err := dependentTestJob.GetParent() + ptj, err := dependentTestJob.GetParent(s) assert.NoError(t, err) assert.Equal(t, parentTestJob.Name, ptj.Name) @@ -60,7 +58,7 @@ func TestJobGetParent(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "", dtj.ParentJob) - ptj, err = dtj.GetParent() + ptj, err = dtj.GetParent(s) assert.EqualError(t, ErrNoParent, err.Error()) ptj, err = s.GetJob(parentTestJob.Name, nil) @@ -110,9 +108,10 @@ func Test_isRunnable(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(dir) - a := &Agent{} - s, err := NewStore(a, dir) - a.Store = s + s, err := NewStore(dir) + a := &Agent{ + Store: s, + } defer s.Shutdown() require.NoError(t, err) diff --git a/dkron/leader.go b/dkron/leader.go index 14744167f..a1f2f20a9 100644 --- a/dkron/leader.go +++ b/dkron/leader.go @@ -192,7 +192,7 @@ func (a *Agent) establishLeadership(stopCh chan struct{}) error { if err != nil { log.Fatal(err) } - a.sched.Start(jobs) + a.sched.Start(jobs, a) return nil } diff --git a/dkron/options.go b/dkron/options.go index f6524fcdd..4506f7107 100644 --- a/dkron/options.go +++ b/dkron/options.go @@ -18,3 +18,10 @@ func WithTransportCredentials(tls *tls.Config) AgentOption { agent.TLSConfig = tls } } + +// WithStore set store in the agent +func WithStore(store Storage) AgentOption { + return func(agent *Agent) { + agent.Store = store + } +} diff --git a/dkron/queries_test.go b/dkron/queries_test.go index 36b129e18..11f090dfe 100644 --- a/dkron/queries_test.go +++ b/dkron/queries_test.go @@ -31,7 +31,8 @@ func TestRunQuery(t *testing.T) { c.BootstrapExpect = 1 a := NewAgent(c) - a.Start() + err = a.Start() + require.NoError(t, err) time.Sleep(2 * time.Second) // Test error with no job @@ -45,8 +46,10 @@ func TestRunQuery(t *testing.T) { err = a.Store.SetJob(j1, false) require.NoError(t, err) - a.sched.Start([]*Job{j1}) + a.sched.Start([]*Job{j1}, a) _, err = a.RunQuery("test_job", &Execution{}) assert.NoError(t, err) + + a.Stop() } diff --git a/dkron/scheduler.go b/dkron/scheduler.go index 064ab3774..590880e9a 100644 --- a/dkron/scheduler.go +++ b/dkron/scheduler.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/armon/go-metrics" - "github.com/robfig/cron/v3" "github.com/distribworks/dkron/v2/extcron" + "github.com/robfig/cron/v3" "github.com/sirupsen/logrus" ) @@ -39,11 +39,12 @@ func NewScheduler() *Scheduler { // Start the cron scheduler, adding its corresponding jobs and // executing them on time. -func (s *Scheduler) Start(jobs []*Job) error { +func (s *Scheduler) Start(jobs []*Job, agent *Agent) error { s.Cron = cron.New(cron.WithParser(extcron.NewParser())) metrics.IncrCounter([]string{"scheduler", "start"}, 1) for _, job := range jobs { + job.Agent = agent s.AddJob(job) } s.Cron.Start() @@ -70,9 +71,9 @@ func (s *Scheduler) Stop() { } // Restart the scheduler -func (s *Scheduler) Restart(jobs []*Job) { +func (s *Scheduler) Restart(jobs []*Job, agent *Agent) { s.Stop() - s.Start(jobs) + s.Start(jobs, agent) } // GetEntry returns a scheduler entry from a snapshot in diff --git a/dkron/scheduler_test.go b/dkron/scheduler_test.go index aa543fc80..562adab0b 100644 --- a/dkron/scheduler_test.go +++ b/dkron/scheduler_test.go @@ -20,7 +20,7 @@ func TestSchedule(t *testing.T) { Owner: "John Dough", OwnerEmail: "foo@bar.com", } - sched.Start([]*Job{testJob1}) + sched.Start([]*Job{testJob1}, &Agent{}) assert.True(t, sched.Started) now := time.Now().Truncate(time.Second) @@ -36,10 +36,11 @@ func TestSchedule(t *testing.T) { Owner: "John Dough", OwnerEmail: "foo@bar.com", } - sched.Restart([]*Job{testJob2}) + sched.Restart([]*Job{testJob2}, &Agent{}) assert.True(t, sched.Started) assert.Len(t, sched.Cron.Entries(), 1) + sched.Stop() } func TestTimezoneAwareJob(t *testing.T) { @@ -52,8 +53,9 @@ func TestTimezoneAwareJob(t *testing.T) { Executor: "shell", ExecutorConfig: map[string]string{"command": "echo 'test1'", "shell": "true"}, } - sched.Start([]*Job{tzJob}) + sched.Start([]*Job{tzJob}, &Agent{}) assert.True(t, sched.Started) assert.Len(t, sched.Cron.Entries(), 1) + sched.Stop() } diff --git a/dkron/store.go b/dkron/store.go index eebc11a9e..74ee55cde 100644 --- a/dkron/store.go +++ b/dkron/store.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "os" "sort" "sync" "time" @@ -32,7 +33,6 @@ var ( // It gives dkron the ability to manipulate its embedded storage // BadgerDB. type Store struct { - agent *Agent db *badger.DB lock *sync.Mutex // for closed bool @@ -44,7 +44,19 @@ type JobOptions struct { } // NewStore creates a new Storage instance. -func NewStore(a *Agent, dir string) (*Store, error) { +func NewStore(dir string) (*Store, error) { + dirExists, err := exists(dir) + if err != nil { + return nil, fmt.Errorf("Ivalid directory %s: %w", dir, err) + } + if !dirExists { + // Try to create the directory + err := os.MkdirAll(dir, 0700) + if err != nil { + return nil, fmt.Errorf("Error creating directory %s: %w", dir, err) + } + } + opts := badger.DefaultOptions(dir). WithLogger(log) @@ -58,9 +70,8 @@ func NewStore(a *Agent, dir string) (*Store, error) { } store := &Store{ - db: db, - agent: a, - lock: &sync.Mutex{}, + db: db, + lock: &sync.Mutex{}, } go store.runGcLoop() @@ -108,14 +119,16 @@ func (s *Store) setJobTxnFunc(pbj *dkronpb.Job) func(txn *badger.Txn) error { } } +// DB is the getter for the BadgerDB instance +func (s *Store) DB() *badger.DB { + return s.db +} + // SetJob stores a job in the storage func (s *Store) SetJob(job *Job, copyDependentJobs bool) error { var pbej dkronpb.Job var ej *Job - // Init the job agent - job.Agent = s.agent - if err := job.Validate(); err != nil { return err } @@ -135,7 +148,6 @@ func (s *Store) SetJob(job *Job, copyDependentJobs bool) error { } ej = NewJobFromProto(&pbej) - ej.Agent = s.agent if ej.Name != "" { // When the job runs, these status vars are updated @@ -193,7 +205,7 @@ func (s *Store) removeFromParent(child *Job) error { return nil } - parent, err := child.GetParent() + parent, err := child.GetParent(s) if err != nil { return err } @@ -221,7 +233,7 @@ func (s *Store) addToParent(child *Job) error { return nil } - parent, err := child.GetParent() + parent, err := child.GetParent(s) if err != nil { return err } @@ -322,7 +334,6 @@ func (s *Store) GetJobs(options *JobOptions) ([]*Job, error) { } job := NewJobFromProto(&pbj) - job.Agent = s.agent if options != nil { if options.Metadata != nil && len(options.Metadata) > 0 && !s.jobHasMetadata(job, options.Metadata) { continue @@ -347,7 +358,6 @@ func (s *Store) GetJob(name string, options *JobOptions) (*Job, error) { } job := NewJobFromProto(&pbj) - job.Agent = s.agent return job, nil } @@ -394,7 +404,6 @@ func (s *Store) DeleteJob(name string) (*Job, error) { return ErrDependentJobs } job = NewJobFromProto(&pbj) - job.Agent = s.agent if err := s.DeleteExecutions(name); err != nil { return err diff --git a/dkron/store_test.go b/dkron/store_test.go index 8485e48a6..e55cee333 100644 --- a/dkron/store_test.go +++ b/dkron/store_test.go @@ -16,7 +16,7 @@ func TestStore(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(dir) - s, err := NewStore(nil, dir) + s, err := NewStore(dir) require.NoError(t, err) defer s.Shutdown() @@ -475,7 +475,7 @@ func setupStore(t *testing.T) (*Store, string) { require.NoError(t, err) a := NewAgent(nil) - s, err := NewStore(a, dir) + s, err := NewStore(dir) require.NoError(t, err) a.Store = s diff --git a/go.mod b/go.mod index 618111f69..0b32e5b4d 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,7 @@ require ( github.com/dnaeon/go-vcr v1.0.1 // indirect github.com/gin-contrib/expvar v0.0.0-20180827025536-251166f58ff2 github.com/gin-contrib/multitemplate v0.0.0-20170922032617-bbc6daf6024b - github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect - github.com/gin-gonic/gin v1.3.0 + github.com/gin-gonic/gin v1.5.0 github.com/gogo/protobuf v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 github.com/golang/protobuf v1.3.2 @@ -29,7 +28,6 @@ require ( github.com/hashicorp/serf v0.8.2 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/jordan-wright/email v0.0.0-20180115032944-94ae17dedda2 - github.com/json-iterator/go v1.1.6 // indirect github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 github.com/mattn/go-shellwords v0.0.0-20160315040826-525bedee691b github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -42,12 +40,10 @@ require ( github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.3.2 github.com/stretchr/testify v1.4.0 - github.com/ugorji/go v1.1.5-pre // indirect golang.org/x/net v0.0.0-20190620200207-3b0461eec859 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db // indirect google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 // indirect google.golang.org/grpc v1.19.1 - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 // indirect ) diff --git a/go.sum b/go.sum index 06530aa13..557317e51 100644 --- a/go.sum +++ b/go.sum @@ -67,9 +67,17 @@ github.com/gin-contrib/multitemplate v0.0.0-20170922032617-bbc6daf6024b h1:D/1/e github.com/gin-contrib/multitemplate v0.0.0-20170922032617-bbc6daf6024b/go.mod h1:62qM8p4crGvNKE413gTzn4eMFin1VOJfMDWMRzHdvqM= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -92,6 +100,8 @@ github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOF github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca h1:wobTb8SE189AuxzEKClyYxiI4nUGWlpVtl13eLiFlOE= @@ -167,6 +177,8 @@ github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswD github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= @@ -177,6 +189,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= @@ -184,6 +198,8 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-shellwords v0.0.0-20160315040826-525bedee691b h1:wdxxNEOv2NWWo/9MbMO/zEMq7DJOcpWQG8CH4YSW1nA= github.com/mattn/go-shellwords v0.0.0-20160315040826-525bedee691b/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= @@ -203,8 +219,10 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= @@ -282,9 +300,13 @@ github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 h1:/Bsw4C+DEdqPj github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= github.com/ugorji/go v1.1.5-pre h1:jyJKFOSEbdOc2HODrf2qcCkYOdq7zzXqA9bhW5oV4fM= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -328,6 +350,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= @@ -361,6 +385,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXa gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= From b7c5b3089701747ed9b7563ad281209816bbb654 Mon Sep 17 00:00:00 2001 From: Victor Castell Date: Wed, 29 Jan 2020 22:44:54 +0100 Subject: [PATCH 2/4] feat: Accept middlewares for API routes --- dkron/api.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dkron/api.go b/dkron/api.go index 4359a7b3d..37435eff2 100644 --- a/dkron/api.go +++ b/dkron/api.go @@ -52,7 +52,7 @@ func (h *HTTPTransport) ServeHTTP() { } // APIRoutes registers the api routes on the gin RouterGroup. -func (h *HTTPTransport) APIRoutes(r *gin.RouterGroup) { +func (h *HTTPTransport) APIRoutes(r *gin.RouterGroup, middleware ...gin.HandlerFunc) { r.GET("/debug/vars", expvar.Handler()) h.Engine.GET("/health", func(c *gin.Context) { @@ -63,6 +63,7 @@ func (h *HTTPTransport) APIRoutes(r *gin.RouterGroup) { r.GET("/v1", h.indexHandler) v1 := r.Group("/v1") + v1.Use(middleware...) v1.GET("/", h.indexHandler) v1.GET("/members", h.membersHandler) v1.GET("/leader", h.leaderHandler) From dcc944f4d5ff7c778a75e906151b7c50a3da32b2 Mon Sep 17 00:00:00 2001 From: Victor Castell Date: Wed, 29 Jan 2020 23:40:37 +0100 Subject: [PATCH 3/4] doc: ACL --- website/content/pro/acls.md | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 website/content/pro/acls.md diff --git a/website/content/pro/acls.md b/website/content/pro/acls.md new file mode 100644 index 000000000..0a8351a37 --- /dev/null +++ b/website/content/pro/acls.md @@ -0,0 +1,94 @@ +--- +title: Access Control +--- + +# Access Control (Preview) + +{{% notice info %}} +This feature is in preview and is subject to big changes +{{% /notice %}} + +Dkron provides an optional Access Control List (ACL) system which can be used to control access to data and APIs. The ACL is Capability-based, relying on policies to determine which fine grained rules can be applied. Dkron's capability based ACL system is very similar to common ACL systems you are used to. + +## ACL System Overview + +Dkron's ACL system is implemented with the CNCF [Open Policy Agent](https://www.openpolicyagent.org/) bringing a powerful system to suit your needs. + +The ACL system is designed to be easy to use and fast to enforce while providing administrative insight. At the highest level, there are two major components to the ACL system: + +* **OPA policy engine.** OPA provices policy decission making [decoupling](https://www.openpolicyagent.org/docs/latest/philosophy/#policy-decoupling) Dkron integrates OPA as a library and provides a default policy rules written in the OPA Policy language that implements a set of enforcing rules on request params to the API that are ready to use for most use cases. You don not need to learn the OPA Policy language to start using Dkron's ACL system, but you can modify the default policy rules to adapt to your use case if you need to. Read more in [OPA Docs](https://www.openpolicyagent.org/docs/latest/) + +* **ACL Policies.** Dkron's ACL policies are simple JSON documents that define patterns to allow access to resources. You can find below an example ACL policy that works with the default OPA policy. The ACL JSON structure is not rigid you can adapt it to add new features in combination with the OPA Policy rules. + +{{% notice note %}} +This guide is based on the usage of the default OPA Rego Policy +{{% /notice %}} + +## Configuring ACLs + +ACLs are not enabled by default and must be enabled. To enable ACLs simply create an ACL policy using the API. Below you can find the most basic example of an ACL policy: + +Basic example policy: +``` +curl localhost:8080/v1/acl/policies -d '{ + "path": { + "/v1": { + "capabilities": [ + "read", + ] + }, + "/v1/**": { + "capabilities": [ + "create", + "read", + "update", + "delete", + "list" + ] + } + } +}' +``` + +This policy allows any request to the API. As you can see paths uses glob patterns, and capabilities allow operations on resources. + +ACLs also allows templating, providing the ability to allow or deny operations to certain resource by patterns without having to hardcode values in policies. + +For example, we can for limit job actions on certain resources based on the provided token via the accepted header `X-Dkron-Token` on the request: + +Example policy: +``` +curl localhost:8080/v1/acl/policies -d '{ + "path": { + "/v1/members": { + "capabilities": ["read"] + }, + "/v1/jobs": { + "capabilities": [ + "list", + "read" + ] + }, + "/v1/jobs/{{.Token}}-*": { + "capabilities": [ + "create", + "read", + "update", + "delete" + ] + } + } +}' +``` + +This policy will allow all operations on jobs starting with `[Token]-job_name`, but will deny manipulation of jobs that doesn't match the pattern. + +## Disable ACLs + +As an administrator you will need to edit policies. Currently to be able to edit ACLs if you get locked out, you need to edit the default Rego file and disable enforcement completely. Edit the file located in `policies/main.rego` and change the `default allow` directive to `true`: + +``` +default allow = false -> true +``` + +This way the policy engine always evaluates to true, allowing any operation again. To restore ACL enforcemen, edit again the `default allow` line and set it back to `false`. From 59b797dd0725c06aaec545ebd5da3168d310829b Mon Sep 17 00:00:00 2001 From: Victor Castell Date: Wed, 29 Jan 2020 23:40:50 +0100 Subject: [PATCH 4/4] doc: fix naming --- website/content/pro/auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/pro/auth.md b/website/content/pro/auth.md index 600332b04..465f6ca6d 100644 --- a/website/content/pro/auth.md +++ b/website/content/pro/auth.md @@ -1,5 +1,5 @@ --- -title: Authorization +title: Authentication --- Dkron Pro has the ability to be configured to use HTTP basic auth.