diff --git a/cmd/cluster-agent/app/app.go b/cmd/cluster-agent/app/app.go index ba1aafdf597742..e86ddcf0382065 100644 --- a/cmd/cluster-agent/app/app.go +++ b/cmd/cluster-agent/app/app.go @@ -333,7 +333,7 @@ func start(cmd *cobra.Command, args []string) error { go func() { defer wg.Done() - if err := runCompliance(mainCtx); err != nil { + if err := runCompliance(mainCtx, apiCl, le.IsLeader); err != nil { log.Errorf("Error while running compliance agent: %v", err) } }() diff --git a/cmd/cluster-agent/app/compliance.go b/cmd/cluster-agent/app/compliance.go index b42c11327d399e..8e6af8b36cd6e7 100644 --- a/cmd/cluster-agent/app/compliance.go +++ b/cmd/cluster-agent/app/compliance.go @@ -29,14 +29,9 @@ import ( "github.com/DataDog/datadog-agent/pkg/util/log" ) -func runCompliance(ctx context.Context) error { - apiCl, err := apiserver.WaitForAPIClient(ctx) - if err != nil { - return err - } - +func runCompliance(ctx context.Context, apiCl *apiserver.APIClient, isLeader func() bool) error { stopper := restart.NewSerialStopper() - if err := startCompliance(stopper, apiCl); err != nil { + if err := startCompliance(stopper, apiCl, isLeader); err != nil { return err } @@ -47,7 +42,7 @@ func runCompliance(ctx context.Context) error { } // TODO: Factorize code with pkg/compliance -func startCompliance(stopper restart.Stopper, apiCl *apiserver.APIClient) error { +func startCompliance(stopper restart.Stopper, apiCl *apiserver.APIClient, isLeader func() bool) error { httpConnectivity := config.HTTPConnectivityFailure if endpoints, err := config.BuildHTTPEndpoints(); err == nil { httpConnectivity = http.CheckConnectivity(endpoints.Main) @@ -105,6 +100,7 @@ func startCompliance(stopper restart.Stopper, apiCl *apiserver.APIClient) error return rule.Scope.Includes(compliance.KubernetesClusterScope) }), checks.WithKubernetesClient(apiCl.DynamicCl), + checks.WithIsLeader(isLeader), ) if err != nil { return err diff --git a/pkg/compliance/checks/builder.go b/pkg/compliance/checks/builder.go index 0c15741a454c9f..a9ba6f65a46510 100644 --- a/pkg/compliance/checks/builder.go +++ b/pkg/compliance/checks/builder.go @@ -122,6 +122,14 @@ func WithKubernetesClient(cli env.KubeClient) BuilderOption { } } +// WithIsLeader allows check runner to know if its a leader instance or not (DCA) +func WithIsLeader(isLeader func() bool) BuilderOption { + return func(b *builder) error { + b.isLeaderFunc = isLeader + return nil + } +} + // SuiteMatcher checks if a compliance suite is included type SuiteMatcher func(*compliance.SuiteMeta) bool @@ -220,6 +228,7 @@ type builder struct { dockerClient env.DockerClient auditClient env.AuditClient kubeClient env.KubeClient + isLeaderFunc func() bool status *status } @@ -506,6 +515,13 @@ func (b *builder) RelativeToHostRoot(path string) string { return b.pathMapper.relativeToHostRoot(path) } +func (b *builder) IsLeader() bool { + if b.isLeaderFunc != nil { + return b.isLeaderFunc() + } + return true +} + func (b *builder) EvaluateFromCache(ev eval.Evaluatable) (interface{}, error) { instance := &eval.Instance{ Functions: eval.FunctionMap{ diff --git a/pkg/compliance/checks/check.go b/pkg/compliance/checks/check.go index 95fa5316fb9815..40191986cc17af 100644 --- a/pkg/compliance/checks/check.go +++ b/pkg/compliance/checks/check.go @@ -77,6 +77,10 @@ func (c *complianceCheck) IsTelemetryEnabled() bool { } func (c *complianceCheck) Run() error { + if !c.IsLeader() { + return nil + } + report, err := c.checkable.check(c) if err != nil { log.Warnf("%s: check run failed: %v", c.ruleID, err) diff --git a/pkg/compliance/checks/check_test.go b/pkg/compliance/checks/check_test.go index 65be9e9d892f2c..1f2b720cfab424 100644 --- a/pkg/compliance/checks/check_test.go +++ b/pkg/compliance/checks/check_test.go @@ -105,6 +105,7 @@ func TestCheckRun(t *testing.T) { } if test.configErr == nil { + env.On("IsLeader").Return(true) env.On("Reporter").Return(reporter) reporter.On("Report", test.expectEvent).Once() checkable.On("check", check).Return(test.checkReport, test.checkErr) @@ -115,3 +116,38 @@ func TestCheckRun(t *testing.T) { }) } } + +func TestCheckRunNoLeader(t *testing.T) { + const ( + ruleID = "rule-id" + resourceType = "resource-type" + resourceID = "resource-id" + ) + + assert := assert.New(t) + + env := &mocks.Env{} + defer env.AssertExpectations(t) + + reporter := &mocks.Reporter{} + defer reporter.AssertExpectations(t) + + checkable := &mockCheckable{} + defer checkable.AssertExpectations(t) + + check := &complianceCheck{ + Env: env, + + ruleID: ruleID, + resourceType: resourceType, + resourceID: resourceID, + checkable: checkable, + } + + // Not leader + env.On("IsLeader").Return(false) + checkable.AssertNotCalled(t, "check") + + err := check.Run() + assert.Nil(err) +} diff --git a/pkg/compliance/checks/env/env.go b/pkg/compliance/checks/env/env.go index 94610ab6742264..cae6e83bfc34b3 100644 --- a/pkg/compliance/checks/env/env.go +++ b/pkg/compliance/checks/env/env.go @@ -31,4 +31,5 @@ type Configuration interface { NormalizeToHostRoot(path string) string RelativeToHostRoot(path string) string EvaluateFromCache(e eval.Evaluatable) (interface{}, error) + IsLeader() bool } diff --git a/pkg/compliance/mocks/audit_client.go b/pkg/compliance/mocks/audit_client.go index 987a1f3c05939a..8c845b2f43ba7e 100644 --- a/pkg/compliance/mocks/audit_client.go +++ b/pkg/compliance/mocks/audit_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks diff --git a/pkg/compliance/mocks/builder.go b/pkg/compliance/mocks/builder.go index df0c31c252edcd..525b1f85efc862 100644 --- a/pkg/compliance/mocks/builder.go +++ b/pkg/compliance/mocks/builder.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks @@ -39,3 +39,19 @@ func (_m *Builder) Close() error { return r0 } + +// GetCheckStatus provides a mock function with given fields: +func (_m *Builder) GetCheckStatus() compliance.CheckStatusList { + ret := _m.Called() + + var r0 compliance.CheckStatusList + if rf, ok := ret.Get(0).(func() compliance.CheckStatusList); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(compliance.CheckStatusList) + } + } + + return r0 +} diff --git a/pkg/compliance/mocks/clients.go b/pkg/compliance/mocks/clients.go index 1502c629e305c9..dbdcc1a6164dea 100644 --- a/pkg/compliance/mocks/clients.go +++ b/pkg/compliance/mocks/clients.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks diff --git a/pkg/compliance/mocks/configuration.go b/pkg/compliance/mocks/configuration.go index f47c825d363b32..ebed2b42249c30 100644 --- a/pkg/compliance/mocks/configuration.go +++ b/pkg/compliance/mocks/configuration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks @@ -63,6 +63,20 @@ func (_m *Configuration) Hostname() string { return r0 } +// IsLeader provides a mock function with given fields: +func (_m *Configuration) IsLeader() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // NormalizeToHostRoot provides a mock function with given fields: path func (_m *Configuration) NormalizeToHostRoot(path string) string { ret := _m.Called(path) diff --git a/pkg/compliance/mocks/docker_client.go b/pkg/compliance/mocks/docker_client.go index a55ee9880f5742..54bb49ddf59547 100644 --- a/pkg/compliance/mocks/docker_client.go +++ b/pkg/compliance/mocks/docker_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks diff --git a/pkg/compliance/mocks/env.go b/pkg/compliance/mocks/env.go index 244b6fe36a4165..14968cabfc529d 100644 --- a/pkg/compliance/mocks/env.go +++ b/pkg/compliance/mocks/env.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks @@ -99,6 +99,20 @@ func (_m *Env) Hostname() string { return r0 } +// IsLeader provides a mock function with given fields: +func (_m *Env) IsLeader() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // KubeClient provides a mock function with given fields: func (_m *Env) KubeClient() env.KubeClient { ret := _m.Called() diff --git a/pkg/compliance/mocks/evaluatable.go b/pkg/compliance/mocks/evaluatable.go index 3c9278512f4dec..3563d522e0c30f 100644 --- a/pkg/compliance/mocks/evaluatable.go +++ b/pkg/compliance/mocks/evaluatable.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks diff --git a/pkg/compliance/mocks/iterator.go b/pkg/compliance/mocks/iterator.go index 2470b145c1dbdd..8eee291773eeb8 100644 --- a/pkg/compliance/mocks/iterator.go +++ b/pkg/compliance/mocks/iterator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks diff --git a/pkg/compliance/mocks/reporter.go b/pkg/compliance/mocks/reporter.go index 8cc372c05d9078..6583b1b9632a98 100644 --- a/pkg/compliance/mocks/reporter.go +++ b/pkg/compliance/mocks/reporter.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks diff --git a/pkg/compliance/mocks/scheduler.go b/pkg/compliance/mocks/scheduler.go index 9a334f9919d8c1..07157ac9ac11e1 100644 --- a/pkg/compliance/mocks/scheduler.go +++ b/pkg/compliance/mocks/scheduler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks