From 838edac22ad86d64bc9c841a3ff15f180c5afba0 Mon Sep 17 00:00:00 2001 From: Matthias Teich Date: Mon, 3 Apr 2023 07:33:06 +0200 Subject: [PATCH 1/7] Fix typo in documentation Signed-off-by: Matthias Teich Signed-off-by: Matthias Teich --- website/docs/workload-resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/workload-resources.md b/website/docs/workload-resources.md index 2edc0284a2a..527b10da71f 100644 --- a/website/docs/workload-resources.md +++ b/website/docs/workload-resources.md @@ -7,7 +7,7 @@ title: Working with Workload Resources [Workload resources](https://kubernetes.io/docs/concepts/workloads/) are Kubernetes resources like Deployments or DaemonSets that create Pods by-way-of a controller. Because many Gatekeeper validation policies are written to enforce against Pods, like those found in the [Gatekeeper policy library](https://www.github.com/open-policy-agent/gatekeeper-library), it is important to recognize that Gatekeeper Pod violation messages will not be directly reported to the user when using the library as those Pods are created from workload resources. -To reject workload resources that can create a resource that violates a constraint, checkout the [Validation of Workflow Resources](expansion.md) feature available in Gatekeeper v3.10+. +To reject workload resources that can create a resource that violates a constraint, checkout the [Validation of Workload Resources](expansion.md) feature available in Gatekeeper v3.10+. ### Example From 27c5153e9d9c221f3ba2ed0d369c7dd343c27d42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 09:37:25 -0700 Subject: [PATCH 2/7] chore: bump sigs.k8s.io/controller-runtime from 0.14.5 to 0.14.6 (#2673) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Matthias Teich --- go.mod | 2 +- go.sum | 4 ++-- vendor/modules.txt | 2 +- vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 7645d57ffa1..dd5a5e1a369 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( k8s.io/klog/v2 v2.80.1 k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 oras.land/oras-go v1.2.2 - sigs.k8s.io/controller-runtime v0.14.5 + sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index eb811282c5b..cb1eff7f3bd 100644 --- a/go.sum +++ b/go.sum @@ -2013,8 +2013,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyz sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 h1:PUuX1qIFv309AT8hF/CdPKDmsG/hn/L8zRX7VvISM3A= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= -sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= -sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= +sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= diff --git a/vendor/modules.txt b/vendor/modules.txt index 54d3ae89fd3..e0e77833561 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1253,7 +1253,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client/metrics sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/common/metrics sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client -# sigs.k8s.io/controller-runtime v0.14.5 +# sigs.k8s.io/controller-runtime v0.14.6 ## explicit; go 1.19 sigs.k8s.io/controller-runtime sigs.k8s.io/controller-runtime/pkg/builder diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go index 9827ea02977..bcb1141a505 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go @@ -452,6 +452,7 @@ func convertToByObject[T any](byGVK map[schema.GroupVersionKind]T, scheme *runti if !ok { return nil, def, fmt.Errorf("object %T for GVK %q does not implement client.Object", obj, gvk) } + cObj.GetObjectKind().SetGroupVersionKind(gvk) if byObject == nil { byObject = map[client.Object]T{} } From 942658e310d7da7b26ccde891294949bcf47b90a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 09:38:04 -0700 Subject: [PATCH 3/7] chore: bump github.com/onsi/gomega from 1.27.5 to 1.27.6 (#2671) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> Signed-off-by: Matthias Teich --- go.mod | 2 +- go.sum | 4 +-- vendor/github.com/onsi/gomega/CHANGELOG.md | 8 +++++ vendor/github.com/onsi/gomega/gomega_dsl.go | 2 +- vendor/github.com/onsi/gomega/matchers.go | 11 ++++--- .../onsi/gomega/matchers/consist_of.go | 29 ++++++++++++------- vendor/modules.txt | 2 +- 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index dd5a5e1a369..151e26cbf91 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 github.com/onsi/ginkgo/v2 v2.9.2 - github.com/onsi/gomega v1.27.5 + github.com/onsi/gomega v1.27.6 github.com/open-policy-agent/cert-controller v0.4.0 github.com/open-policy-agent/frameworks/constraint v0.0.0-20230304011918-d82cbe189211 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index cb1eff7f3bd..b2d04f10b5d 100644 --- a/go.sum +++ b/go.sum @@ -944,8 +944,8 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.27.5 h1:T/X6I0RNFw/kTqgfkZPcQ5KU6vCnWNBGdtrIx2dpGeQ= -github.com/onsi/gomega v1.27.5/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/open-policy-agent/cert-controller v0.4.0 h1:AQntgNq7fsoHgnoKrOk0lpRyab1na09vibeJCX4YBCs= github.com/open-policy-agent/cert-controller v0.4.0/go.mod h1:uOQW+2tMU51vSxy1Yt162oVUTMdqLuotC0aObQxrh6k= github.com/open-policy-agent/frameworks/constraint v0.0.0-20230304011918-d82cbe189211 h1:Ma5rn7r3rPwyP9OXi6zNKjNDsCHYccjcno9M83SHgO0= diff --git a/vendor/github.com/onsi/gomega/CHANGELOG.md b/vendor/github.com/onsi/gomega/CHANGELOG.md index fd8fc02b55d..ef428f6f629 100644 --- a/vendor/github.com/onsi/gomega/CHANGELOG.md +++ b/vendor/github.com/onsi/gomega/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.27.6 + +### Fixes +- Allow collections matchers to work correctly when expected has nil elements [60e7cf3] + +### Maintenance +- updates MatchError godoc comment to also accept a Gomega matcher (#654) [67b869d] + ## 1.27.5 ### Maintenance diff --git a/vendor/github.com/onsi/gomega/gomega_dsl.go b/vendor/github.com/onsi/gomega/gomega_dsl.go index 593bcdc6688..872592bfbc3 100644 --- a/vendor/github.com/onsi/gomega/gomega_dsl.go +++ b/vendor/github.com/onsi/gomega/gomega_dsl.go @@ -22,7 +22,7 @@ import ( "github.com/onsi/gomega/types" ) -const GOMEGA_VERSION = "1.27.5" +const GOMEGA_VERSION = "1.27.6" const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler. If you're using Ginkgo then you probably forgot to put your assertion in an It(). diff --git a/vendor/github.com/onsi/gomega/matchers.go b/vendor/github.com/onsi/gomega/matchers.go index 44056ad643e..b832f3dbaf6 100644 --- a/vendor/github.com/onsi/gomega/matchers.go +++ b/vendor/github.com/onsi/gomega/matchers.go @@ -87,14 +87,17 @@ func Succeed() types.GomegaMatcher { return &matchers.SucceedMatcher{} } -// MatchError succeeds if actual is a non-nil error that matches the passed in string/error. +// MatchError succeeds if actual is a non-nil error that matches the passed in +// string, error, or matcher. // // These are valid use-cases: // -// Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error" -// Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual) +// Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error" +// Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual) +// Expect(err).Should(MatchError(ContainsSubstring("sprocket not found"))) // asserts that edrr.Error() contains substring "sprocket not found" // -// It is an error for err to be nil or an object that does not implement the Error interface +// It is an error for err to be nil or an object that does not implement the +// Error interface func MatchError(expected interface{}) types.GomegaMatcher { return &matchers.MatchErrorMatcher{ Expected: expected, diff --git a/vendor/github.com/onsi/gomega/matchers/consist_of.go b/vendor/github.com/onsi/gomega/matchers/consist_of.go index e8ef0dee1f4..f69037a4f0e 100644 --- a/vendor/github.com/onsi/gomega/matchers/consist_of.go +++ b/vendor/github.com/onsi/gomega/matchers/consist_of.go @@ -48,11 +48,13 @@ func neighbours(value, matcher interface{}) (bool, error) { func equalMatchersToElements(matchers []interface{}) (elements []interface{}) { for _, matcher := range matchers { - equalMatcher, ok := matcher.(*EqualMatcher) - if ok { - matcher = equalMatcher.Expected + if equalMatcher, ok := matcher.(*EqualMatcher); ok { + elements = append(elements, equalMatcher.Expected) + } else if _, ok := matcher.(*BeNilMatcher); ok { + elements = append(elements, nil) + } else { + elements = append(elements, matcher) } - elements = append(elements, matcher) } return } @@ -72,11 +74,13 @@ func flatten(elems []interface{}) []interface{} { func matchers(expectedElems []interface{}) (matchers []interface{}) { for _, e := range flatten(expectedElems) { - matcher, isMatcher := e.(omegaMatcher) - if !isMatcher { - matcher = &EqualMatcher{Expected: e} + if e == nil { + matchers = append(matchers, &BeNilMatcher{}) + } else if matcher, isMatcher := e.(omegaMatcher); isMatcher { + matchers = append(matchers, matcher) + } else { + matchers = append(matchers, &EqualMatcher{Expected: e}) } - matchers = append(matchers, matcher) } return } @@ -89,9 +93,14 @@ func presentable(elems []interface{}) interface{} { } sv := reflect.ValueOf(elems) - tt := sv.Index(0).Elem().Type() + firstEl := sv.Index(0) + if firstEl.IsNil() { + return elems + } + tt := firstEl.Elem().Type() for i := 1; i < sv.Len(); i++ { - if sv.Index(i).Elem().Type() != tt { + el := sv.Index(i) + if el.IsNil() || (sv.Index(i).Elem().Type() != tt) { return elems } } diff --git a/vendor/modules.txt b/vendor/modules.txt index e0e77833561..367fd366896 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -400,7 +400,7 @@ github.com/onsi/ginkgo/v2/internal/parallel_support github.com/onsi/ginkgo/v2/internal/testingtproxy github.com/onsi/ginkgo/v2/reporters github.com/onsi/ginkgo/v2/types -# github.com/onsi/gomega v1.27.5 +# github.com/onsi/gomega v1.27.6 ## explicit; go 1.18 github.com/onsi/gomega github.com/onsi/gomega/format From 540270da8e9b552af408b65cac093d5ed55c1c1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:06:08 -0700 Subject: [PATCH 4/7] chore: bump github.com/go-logr/logr from 1.2.3 to 1.2.4 (#2672) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Matthias Teich --- go.mod | 2 +- go.sum | 3 +- vendor/github.com/go-logr/logr/.golangci.yaml | 3 - vendor/github.com/go-logr/logr/discard.go | 32 +--- vendor/github.com/go-logr/logr/funcr/funcr.go | 27 ++- vendor/github.com/go-logr/logr/logr.go | 166 +++++++++++------- vendor/modules.txt | 2 +- 7 files changed, 130 insertions(+), 105 deletions(-) diff --git a/go.mod b/go.mod index 151e26cbf91..1f17517501c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.14 github.com/davecgh/go-spew v1.1.1 github.com/ghodss/yaml v1.0.0 - github.com/go-logr/logr v1.2.3 + github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.3 github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index b2d04f10b5d..6ca08584961 100644 --- a/go.sum +++ b/go.sum @@ -478,8 +478,9 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= diff --git a/vendor/github.com/go-logr/logr/.golangci.yaml b/vendor/github.com/go-logr/logr/.golangci.yaml index 94ff801df1a..0cffafa7bf9 100644 --- a/vendor/github.com/go-logr/logr/.golangci.yaml +++ b/vendor/github.com/go-logr/logr/.golangci.yaml @@ -6,7 +6,6 @@ linters: disable-all: true enable: - asciicheck - - deadcode - errcheck - forcetypeassert - gocritic @@ -18,10 +17,8 @@ linters: - misspell - revive - staticcheck - - structcheck - typecheck - unused - - varcheck issues: exclude-use-default: false diff --git a/vendor/github.com/go-logr/logr/discard.go b/vendor/github.com/go-logr/logr/discard.go index 9d92a38f1d7..99fe8be93c1 100644 --- a/vendor/github.com/go-logr/logr/discard.go +++ b/vendor/github.com/go-logr/logr/discard.go @@ -20,35 +20,5 @@ package logr // used whenever the caller is not interested in the logs. Logger instances // produced by this function always compare as equal. func Discard() Logger { - return Logger{ - level: 0, - sink: discardLogSink{}, - } -} - -// discardLogSink is a LogSink that discards all messages. -type discardLogSink struct{} - -// Verify that it actually implements the interface -var _ LogSink = discardLogSink{} - -func (l discardLogSink) Init(RuntimeInfo) { -} - -func (l discardLogSink) Enabled(int) bool { - return false -} - -func (l discardLogSink) Info(int, string, ...interface{}) { -} - -func (l discardLogSink) Error(error, string, ...interface{}) { -} - -func (l discardLogSink) WithValues(...interface{}) LogSink { - return l -} - -func (l discardLogSink) WithName(string) LogSink { - return l + return New(nil) } diff --git a/vendor/github.com/go-logr/logr/funcr/funcr.go b/vendor/github.com/go-logr/logr/funcr/funcr.go index 7accdb0c400..e52f0cd01e2 100644 --- a/vendor/github.com/go-logr/logr/funcr/funcr.go +++ b/vendor/github.com/go-logr/logr/funcr/funcr.go @@ -21,13 +21,13 @@ limitations under the License. // github.com/go-logr/logr.LogSink with output through an arbitrary // "write" function. See New and NewJSON for details. // -// Custom LogSinks +// # Custom LogSinks // // For users who need more control, a funcr.Formatter can be embedded inside // your own custom LogSink implementation. This is useful when the LogSink // needs to implement additional methods, for example. // -// Formatting +// # Formatting // // This will respect logr.Marshaler, fmt.Stringer, and error interfaces for // values which are being logged. When rendering a struct, funcr will use Go's @@ -37,6 +37,7 @@ package funcr import ( "bytes" "encoding" + "encoding/json" "fmt" "path/filepath" "reflect" @@ -217,7 +218,7 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter { prefix: "", values: nil, depth: 0, - opts: opts, + opts: &opts, } return f } @@ -231,7 +232,7 @@ type Formatter struct { values []interface{} valuesStr string depth int - opts Options + opts *Options } // outputFormat indicates which outputFormat to use. @@ -447,6 +448,7 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s if flags&flagRawStruct == 0 { buf.WriteByte('{') } + printComma := false // testing i>0 is not enough because of JSON omitted fields for i := 0; i < t.NumField(); i++ { fld := t.Field(i) if fld.PkgPath != "" { @@ -478,9 +480,10 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s if omitempty && isEmpty(v.Field(i)) { continue } - if i > 0 { + if printComma { buf.WriteByte(',') } + printComma = true // if we got here, we are rendering a field if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) continue @@ -500,6 +503,20 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s } return buf.String() case reflect.Slice, reflect.Array: + // If this is outputing as JSON make sure this isn't really a json.RawMessage. + // If so just emit "as-is" and don't pretty it as that will just print + // it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want. + if f.outputFormat == outputJSON { + if rm, ok := value.(json.RawMessage); ok { + // If it's empty make sure we emit an empty value as the array style would below. + if len(rm) > 0 { + buf.Write(rm) + } else { + buf.WriteString("null") + } + return buf.String() + } + } buf.WriteByte('[') for i := 0; i < v.Len(); i++ { if i > 0 { diff --git a/vendor/github.com/go-logr/logr/logr.go b/vendor/github.com/go-logr/logr/logr.go index c3b56b3d2c5..e027aea3fd3 100644 --- a/vendor/github.com/go-logr/logr/logr.go +++ b/vendor/github.com/go-logr/logr/logr.go @@ -21,7 +21,7 @@ limitations under the License. // to back that API. Packages in the Go ecosystem can depend on this package, // while callers can implement logging with whatever backend is appropriate. // -// Usage +// # Usage // // Logging is done using a Logger instance. Logger is a concrete type with // methods, which defers the actual logging to a LogSink interface. The main @@ -30,16 +30,20 @@ limitations under the License. // "structured logging". // // With Go's standard log package, we might write: -// log.Printf("setting target value %s", targetValue) +// +// log.Printf("setting target value %s", targetValue) // // With logr's structured logging, we'd write: -// logger.Info("setting target", "value", targetValue) +// +// logger.Info("setting target", "value", targetValue) // // Errors are much the same. Instead of: -// log.Printf("failed to open the pod bay door for user %s: %v", user, err) +// +// log.Printf("failed to open the pod bay door for user %s: %v", user, err) // // We'd write: -// logger.Error(err, "failed to open the pod bay door", "user", user) +// +// logger.Error(err, "failed to open the pod bay door", "user", user) // // Info() and Error() are very similar, but they are separate methods so that // LogSink implementations can choose to do things like attach additional @@ -47,7 +51,7 @@ limitations under the License. // always logged, regardless of the current verbosity. If there is no error // instance available, passing nil is valid. // -// Verbosity +// # Verbosity // // Often we want to log information only when the application in "verbose // mode". To write log lines that are more verbose, Logger has a V() method. @@ -58,20 +62,22 @@ limitations under the License. // Error messages do not have a verbosity level and are always logged. // // Where we might have written: -// if flVerbose >= 2 { -// log.Printf("an unusual thing happened") -// } +// +// if flVerbose >= 2 { +// log.Printf("an unusual thing happened") +// } // // We can write: -// logger.V(2).Info("an unusual thing happened") // -// Logger Names +// logger.V(2).Info("an unusual thing happened") +// +// # Logger Names // // Logger instances can have name strings so that all messages logged through // that instance have additional context. For example, you might want to add // a subsystem name: // -// logger.WithName("compactor").Info("started", "time", time.Now()) +// logger.WithName("compactor").Info("started", "time", time.Now()) // // The WithName() method returns a new Logger, which can be passed to // constructors or other functions for further use. Repeated use of WithName() @@ -82,25 +88,27 @@ limitations under the License. // joining operation (e.g. whitespace, commas, periods, slashes, brackets, // quotes, etc). // -// Saved Values +// # Saved Values // // Logger instances can store any number of key/value pairs, which will be // logged alongside all messages logged through that instance. For example, // you might want to create a Logger instance per managed object: // // With the standard log package, we might write: -// log.Printf("decided to set field foo to value %q for object %s/%s", -// targetValue, object.Namespace, object.Name) +// +// log.Printf("decided to set field foo to value %q for object %s/%s", +// targetValue, object.Namespace, object.Name) // // With logr we'd write: -// // Elsewhere: set up the logger to log the object name. -// obj.logger = mainLogger.WithValues( -// "name", obj.name, "namespace", obj.namespace) // -// // later on... -// obj.logger.Info("setting foo", "value", targetValue) +// // Elsewhere: set up the logger to log the object name. +// obj.logger = mainLogger.WithValues( +// "name", obj.name, "namespace", obj.namespace) +// +// // later on... +// obj.logger.Info("setting foo", "value", targetValue) // -// Best Practices +// # Best Practices // // Logger has very few hard rules, with the goal that LogSink implementations // might have a lot of freedom to differentiate. There are, however, some @@ -124,15 +132,15 @@ limitations under the License. // around. For cases where passing a logger is optional, a pointer to Logger // should be used. // -// Key Naming Conventions +// # Key Naming Conventions // // Keys are not strictly required to conform to any specification or regex, but // it is recommended that they: -// * be human-readable and meaningful (not auto-generated or simple ordinals) -// * be constant (not dependent on input data) -// * contain only printable characters -// * not contain whitespace or punctuation -// * use lower case for simple keys and lowerCamelCase for more complex ones +// - be human-readable and meaningful (not auto-generated or simple ordinals) +// - be constant (not dependent on input data) +// - contain only printable characters +// - not contain whitespace or punctuation +// - use lower case for simple keys and lowerCamelCase for more complex ones // // These guidelines help ensure that log data is processed properly regardless // of the log implementation. For example, log implementations will try to @@ -141,51 +149,54 @@ limitations under the License. // While users are generally free to use key names of their choice, it's // generally best to avoid using the following keys, as they're frequently used // by implementations: -// * "caller": the calling information (file/line) of a particular log line -// * "error": the underlying error value in the `Error` method -// * "level": the log level -// * "logger": the name of the associated logger -// * "msg": the log message -// * "stacktrace": the stack trace associated with a particular log line or -// error (often from the `Error` message) -// * "ts": the timestamp for a log line +// - "caller": the calling information (file/line) of a particular log line +// - "error": the underlying error value in the `Error` method +// - "level": the log level +// - "logger": the name of the associated logger +// - "msg": the log message +// - "stacktrace": the stack trace associated with a particular log line or +// error (often from the `Error` message) +// - "ts": the timestamp for a log line // // Implementations are encouraged to make use of these keys to represent the // above concepts, when necessary (for example, in a pure-JSON output form, it // would be necessary to represent at least message and timestamp as ordinary // named values). // -// Break Glass +// # Break Glass // // Implementations may choose to give callers access to the underlying // logging implementation. The recommended pattern for this is: -// // Underlier exposes access to the underlying logging implementation. -// // Since callers only have a logr.Logger, they have to know which -// // implementation is in use, so this interface is less of an abstraction -// // and more of way to test type conversion. -// type Underlier interface { -// GetUnderlying() -// } +// +// // Underlier exposes access to the underlying logging implementation. +// // Since callers only have a logr.Logger, they have to know which +// // implementation is in use, so this interface is less of an abstraction +// // and more of way to test type conversion. +// type Underlier interface { +// GetUnderlying() +// } // // Logger grants access to the sink to enable type assertions like this: -// func DoSomethingWithImpl(log logr.Logger) { -// if underlier, ok := log.GetSink()(impl.Underlier) { -// implLogger := underlier.GetUnderlying() -// ... -// } -// } +// +// func DoSomethingWithImpl(log logr.Logger) { +// if underlier, ok := log.GetSink().(impl.Underlier); ok { +// implLogger := underlier.GetUnderlying() +// ... +// } +// } // // Custom `With*` functions can be implemented by copying the complete // Logger struct and replacing the sink in the copy: -// // WithFooBar changes the foobar parameter in the log sink and returns a -// // new logger with that modified sink. It does nothing for loggers where -// // the sink doesn't support that parameter. -// func WithFoobar(log logr.Logger, foobar int) logr.Logger { -// if foobarLogSink, ok := log.GetSink()(FoobarSink); ok { -// log = log.WithSink(foobarLogSink.WithFooBar(foobar)) -// } -// return log -// } +// +// // WithFooBar changes the foobar parameter in the log sink and returns a +// // new logger with that modified sink. It does nothing for loggers where +// // the sink doesn't support that parameter. +// func WithFoobar(log logr.Logger, foobar int) logr.Logger { +// if foobarLogSink, ok := log.GetSink().(FoobarSink); ok { +// log = log.WithSink(foobarLogSink.WithFooBar(foobar)) +// } +// return log +// } // // Don't use New to construct a new Logger with a LogSink retrieved from an // existing Logger. Source code attribution might not work correctly and @@ -201,11 +212,14 @@ import ( ) // New returns a new Logger instance. This is primarily used by libraries -// implementing LogSink, rather than end users. +// implementing LogSink, rather than end users. Passing a nil sink will create +// a Logger which discards all log lines. func New(sink LogSink) Logger { logger := Logger{} logger.setSink(sink) - sink.Init(runtimeInfo) + if sink != nil { + sink.Init(runtimeInfo) + } return logger } @@ -244,7 +258,7 @@ type Logger struct { // Enabled tests whether this Logger is enabled. For example, commandline // flags might be used to set the logging verbosity and disable some info logs. func (l Logger) Enabled() bool { - return l.sink.Enabled(l.level) + return l.sink != nil && l.sink.Enabled(l.level) } // Info logs a non-error message with the given key/value pairs as context. @@ -254,6 +268,9 @@ func (l Logger) Enabled() bool { // information. The key/value pairs must alternate string keys and arbitrary // values. func (l Logger) Info(msg string, keysAndValues ...interface{}) { + if l.sink == nil { + return + } if l.Enabled() { if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() @@ -273,6 +290,9 @@ func (l Logger) Info(msg string, keysAndValues ...interface{}) { // triggered this log line, if present. The err parameter is optional // and nil may be passed instead of an error instance. func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { + if l.sink == nil { + return + } if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() } @@ -284,6 +304,9 @@ func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { // level means a log message is less important. Negative V-levels are treated // as 0. func (l Logger) V(level int) Logger { + if l.sink == nil { + return l + } if level < 0 { level = 0 } @@ -294,6 +317,9 @@ func (l Logger) V(level int) Logger { // WithValues returns a new Logger instance with additional key/value pairs. // See Info for documentation on how key/value pairs work. func (l Logger) WithValues(keysAndValues ...interface{}) Logger { + if l.sink == nil { + return l + } l.setSink(l.sink.WithValues(keysAndValues...)) return l } @@ -304,6 +330,9 @@ func (l Logger) WithValues(keysAndValues ...interface{}) Logger { // contain only letters, digits, and hyphens (see the package documentation for // more information). func (l Logger) WithName(name string) Logger { + if l.sink == nil { + return l + } l.setSink(l.sink.WithName(name)) return l } @@ -324,6 +353,9 @@ func (l Logger) WithName(name string) Logger { // WithCallDepth(1) because it works with implementions that support the // CallDepthLogSink and/or CallStackHelperLogSink interfaces. func (l Logger) WithCallDepth(depth int) Logger { + if l.sink == nil { + return l + } if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(depth)) } @@ -345,6 +377,9 @@ func (l Logger) WithCallDepth(depth int) Logger { // implementation does not support either of these, the original Logger will be // returned. func (l Logger) WithCallStackHelper() (func(), Logger) { + if l.sink == nil { + return func() {}, l + } var helper func() if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(1)) @@ -357,6 +392,11 @@ func (l Logger) WithCallStackHelper() (func(), Logger) { return helper, l } +// IsZero returns true if this logger is an uninitialized zero value +func (l Logger) IsZero() bool { + return l.sink == nil +} + // contextKey is how we find Loggers in a context.Context. type contextKey struct{} @@ -442,7 +482,7 @@ type LogSink interface { WithName(name string) LogSink } -// CallDepthLogSink represents a Logger that knows how to climb the call stack +// CallDepthLogSink represents a LogSink that knows how to climb the call stack // to identify the original call site and can offset the depth by a specified // number of frames. This is useful for users who have helper functions // between the "real" call site and the actual calls to Logger methods. @@ -467,7 +507,7 @@ type CallDepthLogSink interface { WithCallDepth(depth int) LogSink } -// CallStackHelperLogSink represents a Logger that knows how to climb +// CallStackHelperLogSink represents a LogSink that knows how to climb // the call stack to identify the original call site and can skip // intermediate helper functions if they mark themselves as // helper. Go's testing package uses that approach. diff --git a/vendor/modules.txt b/vendor/modules.txt index 367fd366896..73173e2477c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -203,7 +203,7 @@ github.com/go-kit/log/level # github.com/go-logfmt/logfmt v0.5.1 ## explicit; go 1.17 github.com/go-logfmt/logfmt -# github.com/go-logr/logr v1.2.3 +# github.com/go-logr/logr v1.2.4 ## explicit; go 1.16 github.com/go-logr/logr github.com/go-logr/logr/funcr From 6f8c8ca66251bfffd793ae8f4bf0e9fa12e304ac Mon Sep 17 00:00:00 2001 From: Davis Haba <52938648+davis-haba@users.noreply.github.com> Date: Tue, 4 Apr 2023 17:57:40 -0700 Subject: [PATCH 5/7] feat: implement expansion template pod status (#2598) Signed-off-by: Davis Haba <52938648+davis-haba@users.noreply.github.com> Co-authored-by: Rita Zhang Signed-off-by: Matthias Teich --- .../unversioned/expansiontemplate_types.go | 9 +- .../unversioned/zz_generated.deepcopy.go | 24 ++ ...te_types.go => expansiontemplate_types.go} | 11 +- .../v1alpha1/zz_generated.conversion.go | 37 +++ .../v1alpha1/zz_generated.deepcopy.go | 24 ++ .../v1beta1/constraintpodstatus_types.go | 2 +- .../constrainttemplatepodstatus_types.go | 2 +- .../expansiontemplatepodstatus_types.go | 83 +++++++ apis/status/v1beta1/labels.go | 1 + apis/status/v1beta1/mutatorpodstatus_types.go | 2 +- apis/status/v1beta1/util.go | 10 +- apis/status/v1beta1/util_test.go | 4 +- apis/status/v1beta1/zz_generated.deepcopy.go | 104 +++++++++ cmd/build/helmify/kustomization.yaml | 6 + ...nsion.gatekeeper.sh_expansiontemplate.yaml | 41 ++++ ...eeper.sh_expansiontemplatepodstatuses.yaml | 71 ++++++ config/crd/kustomization.yaml | 1 + config/rbac/role.yaml | 12 + main.go | 4 +- ...siontemplate-customresourcedefinition.yaml | 36 +++ ...atepodstatus-customresourcedefinition.yaml | 62 +++++ .../gatekeeper-manager-role-clusterrole.yaml | 12 + manifest_staging/deploy/gatekeeper.yaml | 111 +++++++++ pkg/controller/add_expansionstatus.go | 24 ++ .../config/config_controller_suite_test.go | 26 +-- .../config/config_controller_test.go | 8 +- ...onstrainttemplate_controller_suite_test.go | 54 +---- .../constrainttemplate_controller_test.go | 192 +++------------- ...inttemplatestatus_controller_suite_test.go | 55 +---- ...onstrainttemplatestatus_controller_test.go | 4 +- .../expansion/expansion_controller.go | 172 +++++++++++--- .../expansion/expansion_controller_test.go | 203 +++++++++++++++++ pkg/controller/expansion/stats_reporter.go | 42 +++- .../expansionstatus_controller.go | 213 ++++++++++++++++++ .../externaldata_controller_suite_test.go | 31 +-- .../externaldata_controller_test.go | 2 +- .../mutators/core/controller_suite_test.go | 24 -- .../mutators/core/controller_test.go | 4 +- pkg/expansion/system.go | 11 +- pkg/readiness/ready_tracker.go | 64 +++++- pkg/readiness/ready_tracker_test.go | 82 ++++++- pkg/readiness/ready_tracker_unit_test.go | 4 +- pkg/readiness/setup.go | 8 +- .../testdata/99-expansion-template.yaml | 15 ++ pkg/readiness/testdata_test.go | 40 ++++ pkg/webhook/policy.go | 20 ++ test/bats/test.bats | 6 + test/testutils/controller.go | 179 +++++++++++++++ test/testutils/manager.go | 38 ++++ 49 files changed, 1753 insertions(+), 437 deletions(-) rename apis/expansion/v1alpha1/{expansion_template_types.go => expansiontemplate_types.go} (86%) create mode 100644 apis/status/v1beta1/expansiontemplatepodstatus_types.go create mode 100644 config/crd/bases/status.gatekeeper.sh_expansiontemplatepodstatuses.yaml create mode 100644 manifest_staging/charts/gatekeeper/crds/expansiontemplatepodstatus-customresourcedefinition.yaml create mode 100644 pkg/controller/add_expansionstatus.go create mode 100644 pkg/controller/expansion/expansion_controller_test.go create mode 100644 pkg/controller/expansionstatus/expansionstatus_controller.go create mode 100644 pkg/readiness/testdata/99-expansion-template.yaml create mode 100644 test/testutils/controller.go diff --git a/apis/expansion/unversioned/expansiontemplate_types.go b/apis/expansion/unversioned/expansiontemplate_types.go index 74d3385a895..91bab506b30 100644 --- a/apis/expansion/unversioned/expansiontemplate_types.go +++ b/apis/expansion/unversioned/expansiontemplate_types.go @@ -16,6 +16,7 @@ limitations under the License. package unversioned import ( + statusv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" "github.com/open-policy-agent/gatekeeper/pkg/mutation/match" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -63,7 +64,13 @@ type ExpansionTemplate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec ExpansionTemplateSpec `json:"spec,omitempty"` + Spec ExpansionTemplateSpec `json:"spec,omitempty"` + Status ExpansionTemplateStatus `json:"status,omitempty"` +} + +// ExpansionTemplateStatus defines the observed state of ExpansionTemplate. +type ExpansionTemplateStatus struct { + ByPod []statusv1alpha1.ExpansionTemplatePodStatusStatus `json:"byPod,omitempty"` } // +kubebuilder:object:root=true diff --git a/apis/expansion/unversioned/zz_generated.deepcopy.go b/apis/expansion/unversioned/zz_generated.deepcopy.go index c3b73060f42..360bbe7849b 100644 --- a/apis/expansion/unversioned/zz_generated.deepcopy.go +++ b/apis/expansion/unversioned/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package unversioned import ( + "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" "github.com/open-policy-agent/gatekeeper/pkg/mutation/match" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -31,6 +32,7 @@ func (in *ExpansionTemplate) DeepCopyInto(out *ExpansionTemplate) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplate. @@ -106,6 +108,28 @@ func (in *ExpansionTemplateSpec) DeepCopy() *ExpansionTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExpansionTemplateStatus) DeepCopyInto(out *ExpansionTemplateStatus) { + *out = *in + if in.ByPod != nil { + in, out := &in.ByPod, &out.ByPod + *out = make([]v1beta1.ExpansionTemplatePodStatusStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplateStatus. +func (in *ExpansionTemplateStatus) DeepCopy() *ExpansionTemplateStatus { + if in == nil { + return nil + } + out := new(ExpansionTemplateStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GeneratedGVK) DeepCopyInto(out *GeneratedGVK) { *out = *in diff --git a/apis/expansion/v1alpha1/expansion_template_types.go b/apis/expansion/v1alpha1/expansiontemplate_types.go similarity index 86% rename from apis/expansion/v1alpha1/expansion_template_types.go rename to apis/expansion/v1alpha1/expansiontemplate_types.go index 3b710013f56..6dcb52938f0 100644 --- a/apis/expansion/v1alpha1/expansion_template_types.go +++ b/apis/expansion/v1alpha1/expansiontemplate_types.go @@ -16,6 +16,7 @@ limitations under the License. package v1alpha1 import ( + status "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" "github.com/open-policy-agent/gatekeeper/pkg/mutation/match" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -56,13 +57,21 @@ type GeneratedGVK struct { // +kubebuilder:object:root=true // +kubebuilder:resource:path="expansiontemplate" // +kubebuilder:resource:scope="Cluster" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion // ExpansionTemplate is the Schema for the ExpansionTemplate API. type ExpansionTemplate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec ExpansionTemplateSpec `json:"spec,omitempty"` + Spec ExpansionTemplateSpec `json:"spec,omitempty"` + Status ExpansionTemplateStatus `json:"status,omitempty"` +} + +// ExpansionTemplateStatus defines the observed state of ExpansionTemplate. +type ExpansionTemplateStatus struct { + ByPod []status.ExpansionTemplatePodStatusStatus `json:"byPod,omitempty"` } // +kubebuilder:object:root=true diff --git a/apis/expansion/v1alpha1/zz_generated.conversion.go b/apis/expansion/v1alpha1/zz_generated.conversion.go index 16c04fddadb..43871d34ad0 100644 --- a/apis/expansion/v1alpha1/zz_generated.conversion.go +++ b/apis/expansion/v1alpha1/zz_generated.conversion.go @@ -23,6 +23,7 @@ import ( unsafe "unsafe" unversioned "github.com/open-policy-agent/gatekeeper/apis/expansion/unversioned" + v1beta1 "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" match "github.com/open-policy-agent/gatekeeper/pkg/mutation/match" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" @@ -65,6 +66,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*ExpansionTemplateStatus)(nil), (*unversioned.ExpansionTemplateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ExpansionTemplateStatus_To_unversioned_ExpansionTemplateStatus(a.(*ExpansionTemplateStatus), b.(*unversioned.ExpansionTemplateStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.ExpansionTemplateStatus)(nil), (*ExpansionTemplateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_ExpansionTemplateStatus_To_v1alpha1_ExpansionTemplateStatus(a.(*unversioned.ExpansionTemplateStatus), b.(*ExpansionTemplateStatus), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*GeneratedGVK)(nil), (*unversioned.GeneratedGVK)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_GeneratedGVK_To_unversioned_GeneratedGVK(a.(*GeneratedGVK), b.(*unversioned.GeneratedGVK), scope) }); err != nil { @@ -83,6 +94,9 @@ func autoConvert_v1alpha1_ExpansionTemplate_To_unversioned_ExpansionTemplate(in if err := Convert_v1alpha1_ExpansionTemplateSpec_To_unversioned_ExpansionTemplateSpec(&in.Spec, &out.Spec, s); err != nil { return err } + if err := Convert_v1alpha1_ExpansionTemplateStatus_To_unversioned_ExpansionTemplateStatus(&in.Status, &out.Status, s); err != nil { + return err + } return nil } @@ -96,6 +110,9 @@ func autoConvert_unversioned_ExpansionTemplate_To_v1alpha1_ExpansionTemplate(in if err := Convert_unversioned_ExpansionTemplateSpec_To_v1alpha1_ExpansionTemplateSpec(&in.Spec, &out.Spec, s); err != nil { return err } + if err := Convert_unversioned_ExpansionTemplateStatus_To_v1alpha1_ExpansionTemplateStatus(&in.Status, &out.Status, s); err != nil { + return err + } return nil } @@ -156,6 +173,26 @@ func Convert_unversioned_ExpansionTemplateSpec_To_v1alpha1_ExpansionTemplateSpec return autoConvert_unversioned_ExpansionTemplateSpec_To_v1alpha1_ExpansionTemplateSpec(in, out, s) } +func autoConvert_v1alpha1_ExpansionTemplateStatus_To_unversioned_ExpansionTemplateStatus(in *ExpansionTemplateStatus, out *unversioned.ExpansionTemplateStatus, s conversion.Scope) error { + out.ByPod = *(*[]v1beta1.ExpansionTemplatePodStatusStatus)(unsafe.Pointer(&in.ByPod)) + return nil +} + +// Convert_v1alpha1_ExpansionTemplateStatus_To_unversioned_ExpansionTemplateStatus is an autogenerated conversion function. +func Convert_v1alpha1_ExpansionTemplateStatus_To_unversioned_ExpansionTemplateStatus(in *ExpansionTemplateStatus, out *unversioned.ExpansionTemplateStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_ExpansionTemplateStatus_To_unversioned_ExpansionTemplateStatus(in, out, s) +} + +func autoConvert_unversioned_ExpansionTemplateStatus_To_v1alpha1_ExpansionTemplateStatus(in *unversioned.ExpansionTemplateStatus, out *ExpansionTemplateStatus, s conversion.Scope) error { + out.ByPod = *(*[]v1beta1.ExpansionTemplatePodStatusStatus)(unsafe.Pointer(&in.ByPod)) + return nil +} + +// Convert_unversioned_ExpansionTemplateStatus_To_v1alpha1_ExpansionTemplateStatus is an autogenerated conversion function. +func Convert_unversioned_ExpansionTemplateStatus_To_v1alpha1_ExpansionTemplateStatus(in *unversioned.ExpansionTemplateStatus, out *ExpansionTemplateStatus, s conversion.Scope) error { + return autoConvert_unversioned_ExpansionTemplateStatus_To_v1alpha1_ExpansionTemplateStatus(in, out, s) +} + func autoConvert_v1alpha1_GeneratedGVK_To_unversioned_GeneratedGVK(in *GeneratedGVK, out *unversioned.GeneratedGVK, s conversion.Scope) error { out.Group = in.Group out.Version = in.Version diff --git a/apis/expansion/v1alpha1/zz_generated.deepcopy.go b/apis/expansion/v1alpha1/zz_generated.deepcopy.go index 8b08913b818..0c35193dba1 100644 --- a/apis/expansion/v1alpha1/zz_generated.deepcopy.go +++ b/apis/expansion/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1alpha1 import ( + "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" "github.com/open-policy-agent/gatekeeper/pkg/mutation/match" "k8s.io/apimachinery/pkg/runtime" ) @@ -31,6 +32,7 @@ func (in *ExpansionTemplate) DeepCopyInto(out *ExpansionTemplate) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplate. @@ -106,6 +108,28 @@ func (in *ExpansionTemplateSpec) DeepCopy() *ExpansionTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExpansionTemplateStatus) DeepCopyInto(out *ExpansionTemplateStatus) { + *out = *in + if in.ByPod != nil { + in, out := &in.ByPod, &out.ByPod + *out = make([]v1beta1.ExpansionTemplatePodStatusStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplateStatus. +func (in *ExpansionTemplateStatus) DeepCopy() *ExpansionTemplateStatus { + if in == nil { + return nil + } + out := new(ExpansionTemplateStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GeneratedGVK) DeepCopyInto(out *GeneratedGVK) { *out = *in diff --git a/apis/status/v1beta1/constraintpodstatus_types.go b/apis/status/v1beta1/constraintpodstatus_types.go index e91fca1dfeb..e344b7d8063 100644 --- a/apis/status/v1beta1/constraintpodstatus_types.go +++ b/apis/status/v1beta1/constraintpodstatus_types.go @@ -113,5 +113,5 @@ func KeyForConstraint(id string, constraint *unstructured.Unstructured) (string, // because K8s requires all lowercase letters for resource names kind := strings.ToLower(constraint.GetObjectKind().GroupVersionKind().Kind) name := constraint.GetName() - return dashPacker(id, kind, name) + return DashPacker(id, kind, name) } diff --git a/apis/status/v1beta1/constrainttemplatepodstatus_types.go b/apis/status/v1beta1/constrainttemplatepodstatus_types.go index 1b9e0e77399..356d53437f3 100644 --- a/apis/status/v1beta1/constrainttemplatepodstatus_types.go +++ b/apis/status/v1beta1/constrainttemplatepodstatus_types.go @@ -88,5 +88,5 @@ func NewConstraintTemplateStatusForPod(pod *corev1.Pod, templateName string, sch // KeyForConstraintTemplate returns a unique status object name given the Pod ID and // a template object. func KeyForConstraintTemplate(id string, templateName string) (string, error) { - return dashPacker(id, templateName) + return DashPacker(id, templateName) } diff --git a/apis/status/v1beta1/expansiontemplatepodstatus_types.go b/apis/status/v1beta1/expansiontemplatepodstatus_types.go new file mode 100644 index 00000000000..067da872862 --- /dev/null +++ b/apis/status/v1beta1/expansiontemplatepodstatus_types.go @@ -0,0 +1,83 @@ +package v1beta1 + +import ( + "github.com/open-policy-agent/gatekeeper/pkg/operations" + "github.com/open-policy-agent/gatekeeper/pkg/util" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// ExpansionTemplatePodStatusStatus defines the observed state of ExpansionTemplatePodStatus. +type ExpansionTemplatePodStatusStatus struct { + // Important: Run "make" to regenerate code after modifying this file + ID string `json:"id,omitempty"` + TemplateUID types.UID `json:"templateUID,omitempty"` + Operations []string `json:"operations,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + Errors []*ExpansionTemplateError `json:"errors,omitempty"` +} + +// +kubebuilder:object:generate=true + +type ExpansionTemplateError struct { + Type string `json:"type,omitempty"` + Message string `json:"message"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Namespaced + +// ExpansionTemplatePodStatus is the Schema for the expansiontemplatepodstatuses API. +type ExpansionTemplatePodStatus struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status ExpansionTemplatePodStatusStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ExpansionTemplatePodStatusList contains a list of ExpansionTemplatePodStatus. +type ExpansionTemplatePodStatusList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ExpansionTemplatePodStatus `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ExpansionTemplatePodStatus{}, &ExpansionTemplatePodStatusList{}) +} + +// NewExpansionTemplateStatusForPod returns an expansion template status object +// that has been initialized with the bare minimum of fields to make it functional +// with the expansion template status controller. +func NewExpansionTemplateStatusForPod(pod *corev1.Pod, templateName string, scheme *runtime.Scheme) (*ExpansionTemplatePodStatus, error) { + obj := &ExpansionTemplatePodStatus{} + name, err := KeyForExpansionTemplate(pod.Name, templateName) + if err != nil { + return nil, err + } + obj.SetName(name) + obj.SetNamespace(util.GetNamespace()) + obj.Status.ID = pod.Name + obj.Status.Operations = operations.AssignedStringList() + obj.SetLabels(map[string]string{ + ExpansionTemplateNameLabel: templateName, + PodLabel: pod.Name, + }) + + if err := controllerutil.SetOwnerReference(pod, obj, scheme); err != nil { + return nil, err + } + + return obj, nil +} + +// KeyForExpansionTemplate returns a unique status object name given the Pod ID and +// a template object. +func KeyForExpansionTemplate(id string, templateName string) (string, error) { + return DashPacker(id, templateName) +} diff --git a/apis/status/v1beta1/labels.go b/apis/status/v1beta1/labels.go index 9456c8f948f..0f0caca91ce 100644 --- a/apis/status/v1beta1/labels.go +++ b/apis/status/v1beta1/labels.go @@ -2,6 +2,7 @@ package v1beta1 // Label keys used for internal gatekeeper operations. const ( + ExpansionTemplateNameLabel = "internal.gatekeeper.sh/expansiontemplate-name" ConstraintNameLabel = "internal.gatekeeper.sh/constraint-name" ConstraintKindLabel = "internal.gatekeeper.sh/constraint-kind" ConstraintTemplateNameLabel = "internal.gatekeeper.sh/constrainttemplate-name" diff --git a/apis/status/v1beta1/mutatorpodstatus_types.go b/apis/status/v1beta1/mutatorpodstatus_types.go index 83ed175d931..58e76faaa0b 100644 --- a/apis/status/v1beta1/mutatorpodstatus_types.go +++ b/apis/status/v1beta1/mutatorpodstatus_types.go @@ -113,5 +113,5 @@ func KeyForMutatorID(id string, mID mtypes.ID) (string, error) { // We must do this because K8s requires all lowercase letters for resource names kind := strings.ToLower(mID.Kind) name := mID.Name - return dashPacker(id, kind, name) + return DashPacker(id, kind, name) } diff --git a/apis/status/v1beta1/util.go b/apis/status/v1beta1/util.go index 58f1cbd117b..decb0905526 100644 --- a/apis/status/v1beta1/util.go +++ b/apis/status/v1beta1/util.go @@ -32,7 +32,7 @@ func dashExtractor(val string) []string { return tokens } -// dashPacker puts a list of strings into a dash-separated format. Note that +// DashPacker puts a list of strings into a dash-separated format. Note that // it cannot handle empty strings, as that makes the dash separator for the empty // string reduce to an escaped dash. This is fine because none of the packed strings // are allowed to be empty. If this changes in the future, we could create a placeholder @@ -43,17 +43,17 @@ func dashExtractor(val string) []string { // which is also disallowed by the schema (and would require an additional placeholder // character to fix). Finally, note that it is impossible to distinguish between // a nil list of strings and a list of one empty string. -func dashPacker(vals ...string) (string, error) { +func DashPacker(vals ...string) (string, error) { if len(vals) == 0 { - return "", fmt.Errorf("dashPacker cannot pack an empty list of strings") + return "", fmt.Errorf("DashPacker cannot pack an empty list of strings") } b := strings.Builder{} for i, val := range vals { if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { - return "", fmt.Errorf("dashPacker cannot pack strings that begin or end with a dash: %+v", vals) + return "", fmt.Errorf("DashPacker cannot pack strings that begin or end with a dash: %+v", vals) } if len(val) == 0 { - return "", fmt.Errorf("dashPacker cannot pack empty strings: %v", vals) + return "", fmt.Errorf("DashPacker cannot pack empty strings: %v", vals) } if i != 0 { b.WriteString("-") diff --git a/apis/status/v1beta1/util_test.go b/apis/status/v1beta1/util_test.go index 592b9bd1226..47e4a58db20 100644 --- a/apis/status/v1beta1/util_test.go +++ b/apis/status/v1beta1/util_test.go @@ -91,12 +91,12 @@ var dashingTestCases = []struct { func TestDashPacker(t *testing.T) { for _, tc := range dashingTestCases { t.Run(tc.name, func(t *testing.T) { - gotPacked, err := dashPacker(tc.extracted...) + gotPacked, err := DashPacker(tc.extracted...) if err != nil { t.Fatal(err) } if diff := cmp.Diff(tc.packed, gotPacked); diff != "" { - t.Fatal("got dashPacker(tc.extracted...) != tc.packed, want equal") + t.Fatal("got DashPacker(tc.extracted...) != tc.packed, want equal") } }) } diff --git a/apis/status/v1beta1/zz_generated.deepcopy.go b/apis/status/v1beta1/zz_generated.deepcopy.go index dddc9496679..5a42224d349 100644 --- a/apis/status/v1beta1/zz_generated.deepcopy.go +++ b/apis/status/v1beta1/zz_generated.deepcopy.go @@ -212,6 +212,110 @@ func (in *Error) DeepCopy() *Error { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExpansionTemplateError) DeepCopyInto(out *ExpansionTemplateError) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplateError. +func (in *ExpansionTemplateError) DeepCopy() *ExpansionTemplateError { + if in == nil { + return nil + } + out := new(ExpansionTemplateError) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExpansionTemplatePodStatus) DeepCopyInto(out *ExpansionTemplatePodStatus) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplatePodStatus. +func (in *ExpansionTemplatePodStatus) DeepCopy() *ExpansionTemplatePodStatus { + if in == nil { + return nil + } + out := new(ExpansionTemplatePodStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExpansionTemplatePodStatus) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExpansionTemplatePodStatusList) DeepCopyInto(out *ExpansionTemplatePodStatusList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ExpansionTemplatePodStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplatePodStatusList. +func (in *ExpansionTemplatePodStatusList) DeepCopy() *ExpansionTemplatePodStatusList { + if in == nil { + return nil + } + out := new(ExpansionTemplatePodStatusList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExpansionTemplatePodStatusList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExpansionTemplatePodStatusStatus) DeepCopyInto(out *ExpansionTemplatePodStatusStatus) { + *out = *in + if in.Operations != nil { + in, out := &in.Operations, &out.Operations + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]*ExpansionTemplateError, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ExpansionTemplateError) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpansionTemplatePodStatusStatus. +func (in *ExpansionTemplatePodStatusStatus) DeepCopy() *ExpansionTemplatePodStatusStatus { + if in == nil { + return nil + } + out := new(ExpansionTemplatePodStatusStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MutatorError) DeepCopyInto(out *MutatorError) { *out = *in diff --git a/cmd/build/helmify/kustomization.yaml b/cmd/build/helmify/kustomization.yaml index e6507f7114d..a7c4a4647ac 100644 --- a/cmd/build/helmify/kustomization.yaml +++ b/cmd/build/helmify/kustomization.yaml @@ -34,6 +34,12 @@ patchesJson6902: kind: CustomResourceDefinition name: mutatorpodstatuses.status.gatekeeper.sh path: labels_patch.yaml + - target: + group: apiextensions.k8s.io + version: v1 + kind: CustomResourceDefinition + name: expansiontemplatepodstatuses.status.gatekeeper.sh + path: labels_patch.yaml - target: group: apiextensions.k8s.io version: v1 diff --git a/config/crd/bases/expansion.gatekeeper.sh_expansiontemplate.yaml b/config/crd/bases/expansion.gatekeeper.sh_expansiontemplate.yaml index 12f7f40d95e..57570c632c4 100644 --- a/config/crd/bases/expansion.gatekeeper.sh_expansiontemplate.yaml +++ b/config/crd/bases/expansion.gatekeeper.sh_expansiontemplate.yaml @@ -79,6 +79,47 @@ spec: generators, this is usually spec.template type: string type: object + status: + description: ExpansionTemplateStatus defines the observed state of ExpansionTemplate. + properties: + byPod: + items: + description: ExpansionTemplatePodStatusStatus defines the observed + state of ExpansionTemplatePodStatus. + properties: + errors: + items: + properties: + message: + type: string + type: + type: string + required: + - message + type: object + type: array + id: + description: 'Important: Run "make" to regenerate code after + modifying this file' + type: string + observedGeneration: + format: int64 + type: integer + operations: + items: + type: string + type: array + templateUID: + description: UID is a type that holds unique ID values, including + UUIDs. Because we don't ONLY use UUIDs, this is an alias + to string. Being a type captures intent and helps make sure + that UIDs and names do not get conflated. + type: string + type: object + type: array + type: object type: object served: true storage: true + subresources: + status: {} diff --git a/config/crd/bases/status.gatekeeper.sh_expansiontemplatepodstatuses.yaml b/config/crd/bases/status.gatekeeper.sh_expansiontemplatepodstatuses.yaml new file mode 100644 index 00000000000..4335d45f5ca --- /dev/null +++ b/config/crd/bases/status.gatekeeper.sh_expansiontemplatepodstatuses.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: expansiontemplatepodstatuses.status.gatekeeper.sh +spec: + group: status.gatekeeper.sh + names: + kind: ExpansionTemplatePodStatus + listKind: ExpansionTemplatePodStatusList + plural: expansiontemplatepodstatuses + singular: expansiontemplatepodstatus + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: ExpansionTemplatePodStatus is the Schema for the expansiontemplatepodstatuses + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + status: + description: ExpansionTemplatePodStatusStatus defines the observed state + of ExpansionTemplatePodStatus. + properties: + errors: + items: + properties: + message: + type: string + type: + type: string + required: + - message + type: object + type: array + id: + description: 'Important: Run "make" to regenerate code after modifying + this file' + type: string + observedGeneration: + format: int64 + type: integer + operations: + items: + type: string + type: array + templateUID: + description: UID is a type that holds unique ID values, including + UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being + a type captures intent and helps make sure that UIDs and names do + not get conflated. + type: string + type: object + type: object + served: true + storage: true diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index c2b8e350482..475b34b63de 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,6 +6,7 @@ resources: - bases/status.gatekeeper.sh_constraintpodstatuses.yaml - bases/status.gatekeeper.sh_constrainttemplatepodstatuses.yaml - bases/status.gatekeeper.sh_mutatorpodstatuses.yaml +- bases/status.gatekeeper.sh_expansiontemplatepodstatuses.yaml - bases/mutations.gatekeeper.sh_assign.yaml - bases/mutations.gatekeeper.sh_assignimage.yaml - bases/mutations.gatekeeper.sh_assignmetadata.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index b81a0171252..cb1aaf143ae 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -76,6 +76,18 @@ rules: - patch - update - watch +- apiGroups: + - expansion.gatekeeper.sh + resources: + - '*' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - externaldata.gatekeeper.sh resources: diff --git a/main.go b/main.go index a4be1697904..a08a1cb41e3 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ import ( frameworksexternaldata "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" api "github.com/open-policy-agent/gatekeeper/apis" configv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/config/v1alpha1" + expansionv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/expansion/v1alpha1" mutationsv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/mutations/v1alpha1" mutationsv1beta1 "github.com/open-policy-agent/gatekeeper/apis/mutations/v1beta1" statusv1beta1 "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" @@ -116,6 +117,7 @@ func init() { _ = statusv1beta1.AddToScheme(scheme) _ = mutationsv1alpha1.AddToScheme(scheme) _ = mutationsv1beta1.AddToScheme(scheme) + _ = expansionv1alpha1.AddToScheme(scheme) // +kubebuilder:scaffold:scheme flag.Var(disabledBuiltins, "disable-opa-builtin", "disable opa built-in function, this flag can be declared more than once.") @@ -259,7 +261,7 @@ func innerMain() int { sw := watch.NewSwitch() // Setup tracker and register readiness probe. - tracker, err := readiness.SetupTracker(mgr, mutation.Enabled(), *externaldata.ExternalDataEnabled) + tracker, err := readiness.SetupTracker(mgr, mutation.Enabled(), *externaldata.ExternalDataEnabled, *expansion.ExpansionEnabled) if err != nil { setupLog.Error(err, "unable to register readiness tracker") return 1 diff --git a/manifest_staging/charts/gatekeeper/crds/expansiontemplate-customresourcedefinition.yaml b/manifest_staging/charts/gatekeeper/crds/expansiontemplate-customresourcedefinition.yaml index 042249cf102..beae74edbf9 100644 --- a/manifest_staging/charts/gatekeeper/crds/expansiontemplate-customresourcedefinition.yaml +++ b/manifest_staging/charts/gatekeeper/crds/expansiontemplate-customresourcedefinition.yaml @@ -68,6 +68,42 @@ spec: description: TemplateSource specifies the source field on the generator resource to use as the base for expanded resource. For Pod-creating generators, this is usually spec.template type: string type: object + status: + description: ExpansionTemplateStatus defines the observed state of ExpansionTemplate. + properties: + byPod: + items: + description: ExpansionTemplatePodStatusStatus defines the observed state of ExpansionTemplatePodStatus. + properties: + errors: + items: + properties: + message: + type: string + type: + type: string + required: + - message + type: object + type: array + id: + description: 'Important: Run "make" to regenerate code after modifying this file' + type: string + observedGeneration: + format: int64 + type: integer + operations: + items: + type: string + type: array + templateUID: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + type: object + type: array + type: object type: object served: true storage: true + subresources: + status: {} diff --git a/manifest_staging/charts/gatekeeper/crds/expansiontemplatepodstatus-customresourcedefinition.yaml b/manifest_staging/charts/gatekeeper/crds/expansiontemplatepodstatus-customresourcedefinition.yaml new file mode 100644 index 00000000000..8f49b4c5f7f --- /dev/null +++ b/manifest_staging/charts/gatekeeper/crds/expansiontemplatepodstatus-customresourcedefinition.yaml @@ -0,0 +1,62 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + labels: + gatekeeper.sh/system: "yes" + name: expansiontemplatepodstatuses.status.gatekeeper.sh +spec: + group: status.gatekeeper.sh + names: + kind: ExpansionTemplatePodStatus + listKind: ExpansionTemplatePodStatusList + plural: expansiontemplatepodstatuses + singular: expansiontemplatepodstatus + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: ExpansionTemplatePodStatus is the Schema for the expansiontemplatepodstatuses API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + status: + description: ExpansionTemplatePodStatusStatus defines the observed state of ExpansionTemplatePodStatus. + properties: + errors: + items: + properties: + message: + type: string + type: + type: string + required: + - message + type: object + type: array + id: + description: 'Important: Run "make" to regenerate code after modifying this file' + type: string + observedGeneration: + format: int64 + type: integer + operations: + items: + type: string + type: array + templateUID: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + type: object + type: object + served: true + storage: true diff --git a/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml b/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml index a57b2b80c88..3e55923360c 100644 --- a/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml +++ b/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml @@ -82,6 +82,18 @@ rules: - patch - update - watch +- apiGroups: + - expansion.gatekeeper.sh + resources: + - '*' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - externaldata.gatekeeper.sh resources: diff --git a/manifest_staging/deploy/gatekeeper.yaml b/manifest_staging/deploy/gatekeeper.yaml index 98bc929522d..e4d3a734e6b 100644 --- a/manifest_staging/deploy/gatekeeper.yaml +++ b/manifest_staging/deploy/gatekeeper.yaml @@ -2351,6 +2351,105 @@ spec: description: TemplateSource specifies the source field on the generator resource to use as the base for expanded resource. For Pod-creating generators, this is usually spec.template type: string type: object + status: + description: ExpansionTemplateStatus defines the observed state of ExpansionTemplate. + properties: + byPod: + items: + description: ExpansionTemplatePodStatusStatus defines the observed state of ExpansionTemplatePodStatus. + properties: + errors: + items: + properties: + message: + type: string + type: + type: string + required: + - message + type: object + type: array + id: + description: 'Important: Run "make" to regenerate code after modifying this file' + type: string + observedGeneration: + format: int64 + type: integer + operations: + items: + type: string + type: array + templateUID: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + labels: + gatekeeper.sh/system: "yes" + name: expansiontemplatepodstatuses.status.gatekeeper.sh +spec: + group: status.gatekeeper.sh + names: + kind: ExpansionTemplatePodStatus + listKind: ExpansionTemplatePodStatusList + plural: expansiontemplatepodstatuses + singular: expansiontemplatepodstatus + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: ExpansionTemplatePodStatus is the Schema for the expansiontemplatepodstatuses API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + status: + description: ExpansionTemplatePodStatusStatus defines the observed state of ExpansionTemplatePodStatus. + properties: + errors: + items: + properties: + message: + type: string + type: + type: string + required: + - message + type: object + type: array + id: + description: 'Important: Run "make" to regenerate code after modifying this file' + type: string + observedGeneration: + format: int64 + type: integer + operations: + items: + type: string + type: array + templateUID: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + type: object type: object served: true storage: true @@ -3293,6 +3392,18 @@ rules: - patch - update - watch +- apiGroups: + - expansion.gatekeeper.sh + resources: + - '*' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - externaldata.gatekeeper.sh resources: diff --git a/pkg/controller/add_expansionstatus.go b/pkg/controller/add_expansionstatus.go new file mode 100644 index 00000000000..bb97ca2556c --- /dev/null +++ b/pkg/controller/add_expansionstatus.go @@ -0,0 +1,24 @@ +/* + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "github.com/open-policy-agent/gatekeeper/pkg/controller/expansionstatus" +) + +func init() { + Injectors = append(Injectors, &expansionstatus.Adder{}) +} diff --git a/pkg/controller/config/config_controller_suite_test.go b/pkg/controller/config/config_controller_suite_test.go index 82bd38f67b0..1ecb11af9c0 100644 --- a/pkg/controller/config/config_controller_suite_test.go +++ b/pkg/controller/config/config_controller_suite_test.go @@ -23,12 +23,9 @@ import ( "testing" "github.com/open-policy-agent/gatekeeper/apis" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/open-policy-agent/gatekeeper/test/testutils" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -52,7 +49,7 @@ func TestMain(m *testing.M) { stdlog.Fatal(err) } - if err := createGatekeeperNamespace(cfg); err != nil { + if err := testutils.CreateGatekeeperNamespace(cfg); err != nil { stdlog.Printf("creating namespace: %v", err) } @@ -74,22 +71,3 @@ func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan }) return fn, requests } - -// Bootstrap the gatekeeper-system namespace for use in tests. -func createGatekeeperNamespace(cfg *rest.Config) error { - c, err := client.New(cfg, client.Options{}) - if err != nil { - return err - } - - // Create gatekeeper namespace - ns := &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gatekeeper-system", - }, - } - - ctx := context.Background() - _, err = controllerutil.CreateOrUpdate(ctx, c, ns, func() error { return nil }) - return err -} diff --git a/pkg/controller/config/config_controller_test.go b/pkg/controller/config/config_controller_test.go index 4bf600872a4..d843b201ca2 100644 --- a/pkg/controller/config/config_controller_test.go +++ b/pkg/controller/config/config_controller_test.go @@ -138,7 +138,7 @@ func TestReconcile(t *testing.T) { } cs := watch.NewSwitch() - tracker, err := readiness.SetupTracker(mgr, false, false) + tracker, err := readiness.SetupTracker(mgr, false, false, false) if err != nil { t.Fatal(err) } @@ -321,7 +321,7 @@ func TestConfig_DeleteSyncResources(t *testing.T) { } // set up tracker - tracker, err := readiness.SetupTracker(mgr, false, false) + tracker, err := readiness.SetupTracker(mgr, false, false, false) if err != nil { t.Fatal(err) } @@ -431,7 +431,7 @@ func TestConfig_CacheContents(t *testing.T) { opaClient := &fakeOpa{} cs := watch.NewSwitch() - tracker, err := readiness.SetupTracker(mgr, false, false) + tracker, err := readiness.SetupTracker(mgr, false, false, false) if err != nil { t.Fatal(err) } @@ -592,7 +592,7 @@ func TestConfig_Retries(t *testing.T) { opaClient := &fakeOpa{} cs := watch.NewSwitch() - tracker, err := readiness.SetupTracker(mgr, false, false) + tracker, err := readiness.SetupTracker(mgr, false, false, false) if err != nil { t.Fatal(err) } diff --git a/pkg/controller/constrainttemplate/constrainttemplate_controller_suite_test.go b/pkg/controller/constrainttemplate/constrainttemplate_controller_suite_test.go index b61197fd038..115c4b511cc 100644 --- a/pkg/controller/constrainttemplate/constrainttemplate_controller_suite_test.go +++ b/pkg/controller/constrainttemplate/constrainttemplate_controller_suite_test.go @@ -16,64 +16,14 @@ limitations under the License. package constrainttemplate import ( - "context" - "log" - "os" - "path/filepath" "testing" - "github.com/open-policy-agent/gatekeeper/apis" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" + "github.com/open-policy-agent/gatekeeper/test/testutils" "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/envtest" ) var cfg *rest.Config func TestMain(m *testing.M) { - t := &envtest.Environment{ - CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "..", "vendor", "github.com", "open-policy-agent", "frameworks", "constraint", "deploy", "crds.yaml"), - filepath.Join("..", "..", "..", "config", "crd", "bases"), - }, - ErrorIfCRDPathMissing: true, - } - if err := apis.AddToScheme(scheme.Scheme); err != nil { - log.Fatal(err) - } - - var err error - if cfg, err = t.Start(); err != nil { - log.Fatal(err) - } - log.Print("STARTED") - - code := m.Run() - if err = t.Stop(); err != nil { - log.Printf("error while trying to stop server: %v", err) - } - os.Exit(code) -} - -// Bootstrap the gatekeeper-system namespace for use in tests. -func createGatekeeperNamespace(cfg *rest.Config) error { - c, err := client.New(cfg, client.Options{}) - if err != nil { - return err - } - - // Create gatekeeper namespace - ns := &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gatekeeper-system", - }, - } - - ctx := context.Background() - _, err = controllerutil.CreateOrUpdate(ctx, c, ns, func() error { return nil }) - return err + testutils.StartControlPlane(m, &cfg, 3) } diff --git a/pkg/controller/constrainttemplate/constrainttemplate_controller_test.go b/pkg/controller/constrainttemplate/constrainttemplate_controller_test.go index 410923ab226..cfe2495c7ab 100644 --- a/pkg/controller/constrainttemplate/constrainttemplate_controller_test.go +++ b/pkg/controller/constrainttemplate/constrainttemplate_controller_test.go @@ -21,7 +21,6 @@ import ( "fmt" "strings" "testing" - "time" templatesv1 "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1" "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1beta1" @@ -35,70 +34,23 @@ import ( "github.com/open-policy-agent/gatekeeper/pkg/watch" testclient "github.com/open-policy-agent/gatekeeper/test/clients" "github.com/open-policy-agent/gatekeeper/test/testutils" - "github.com/open-policy-agent/gatekeeper/third_party/sigs.k8s.io/controller-runtime/pkg/dynamiccache" - "github.com/prometheus/client_golang/prometheus" "golang.org/x/net/context" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/metrics" ) -// constantRetry makes 3,000 attempts at a rate of 100 per second. Since this -// is a test instance and not a "real" cluster, this is fine and there's no need -// to increase the wait time each iteration. -var constantRetry = wait.Backoff{ - Steps: 3000, - Duration: 10 * time.Millisecond, -} - -// setupManager sets up a controller-runtime manager with registered watch manager. -func setupManager(t *testing.T) (manager.Manager, *watch.Manager) { - t.Helper() - - metrics.Registry = prometheus.NewRegistry() - mgr, err := manager.New(cfg, manager.Options{ - MetricsBindAddress: "0", - NewCache: dynamiccache.New, - MapperProvider: func(c *rest.Config) (meta.RESTMapper, error) { - return apiutil.NewDynamicRESTMapper(c) - }, - Logger: testutils.NewLogger(t), - }) - if err != nil { - t.Fatalf("setting up controller manager: %s", err) - } - c := mgr.GetCache() - dc, ok := c.(watch.RemovableCache) - if !ok { - t.Fatalf("expected dynamic cache, got: %T", c) - } - wm, err := watch.New(dc) - if err != nil { - t.Fatalf("could not create watch manager: %s", err) - } - if err := mgr.Add(wm); err != nil { - t.Fatalf("could not add watch manager to manager: %s", err) - } - return mgr, wm -} - func makeReconcileConstraintTemplate(suffix string) *v1beta1.ConstraintTemplate { return &v1beta1.ConstraintTemplate{ TypeMeta: metav1.TypeMeta{ @@ -151,12 +103,12 @@ func TestReconcile(t *testing.T) { // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. - mgr, wm := setupManager(t) + mgr, wm := testutils.SetupManager(t, cfg) c := testclient.NewRetryClient(mgr.GetClient()) // creating the gatekeeper-system namespace is necessary because that's where // status resources live by default - err := createGatekeeperNamespace(mgr.GetConfig()) + err := testutils.CreateGatekeeperNamespace(mgr.GetConfig()) if err != nil { t.Fatal(err) } @@ -175,7 +127,7 @@ func TestReconcile(t *testing.T) { testutils.Setenv(t, "POD_NAME", "no-pod") cs := watch.NewSwitch() - tracker, err := readiness.SetupTracker(mgr, false, false) + tracker, err := readiness.SetupTracker(mgr, false, false, false) if err != nil { t.Fatal(err) } @@ -205,11 +157,11 @@ func TestReconcile(t *testing.T) { logger.Info("Running test: CRD Gets Created") constraintTemplate := makeReconcileConstraintTemplate(suffix) - t.Cleanup(deleteObjectAndConfirm(ctx, t, c, expectedCRD(suffix))) - createThenCleanup(ctx, t, c, constraintTemplate) + t.Cleanup(testutils.DeleteObjectAndConfirm(ctx, t, c, expectedCRD(suffix))) + testutils.CreateThenCleanup(ctx, t, c, constraintTemplate) clientset := kubernetes.NewForConfigOrDie(cfg) - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { crd := &apiextensionsv1.CustomResourceDefinition{} @@ -239,11 +191,11 @@ func TestReconcile(t *testing.T) { constraintTemplate := makeReconcileConstraintTemplate(suffix) cstr := newDenyAllCstr(suffix) - t.Cleanup(deleteObjectAndConfirm(ctx, t, c, cstr)) - t.Cleanup(deleteObjectAndConfirm(ctx, t, c, expectedCRD(suffix))) - createThenCleanup(ctx, t, c, constraintTemplate) + t.Cleanup(testutils.DeleteObjectAndConfirm(ctx, t, c, cstr)) + t.Cleanup(testutils.DeleteObjectAndConfirm(ctx, t, c, expectedCRD(suffix))) + testutils.CreateThenCleanup(ctx, t, c, constraintTemplate) - err = retry.OnError(constantRetry, func(error) bool { + err = retry.OnError(testutils.ConstantRetry, func(error) bool { return true }, func() error { return c.Create(ctx, cstr) @@ -293,12 +245,12 @@ func TestReconcile(t *testing.T) { constraintTemplate := makeReconcileConstraintTemplate(suffix) cstr := newDenyAllCstr(suffix) - t.Cleanup(deleteObjectAndConfirm(ctx, t, c, cstr)) - t.Cleanup(deleteObjectAndConfirm(ctx, t, c, expectedCRD(suffix))) - createThenCleanup(ctx, t, c, constraintTemplate) + t.Cleanup(testutils.DeleteObjectAndConfirm(ctx, t, c, cstr)) + t.Cleanup(testutils.DeleteObjectAndConfirm(ctx, t, c, expectedCRD(suffix))) + testutils.CreateThenCleanup(ctx, t, c, constraintTemplate) var crd *apiextensionsv1.CustomResourceDefinition - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { crd = &apiextensionsv1.CustomResourceDefinition{} @@ -315,7 +267,7 @@ func TestReconcile(t *testing.T) { t.Fatal(err) } - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { crd := &apiextensionsv1.CustomResourceDefinition{} @@ -339,7 +291,7 @@ func TestReconcile(t *testing.T) { t.Fatal(err) } - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { sList := &statusv1beta1.ConstraintPodStatusList{} @@ -355,7 +307,7 @@ func TestReconcile(t *testing.T) { t.Fatal(err) } - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { return c.Create(ctx, newDenyAllCstr(suffix)) @@ -409,7 +361,7 @@ func TestReconcile(t *testing.T) { // https://github.com/open-policy-agent/gatekeeper/pull/1595#discussion_r722819552 t.Cleanup(testutils.DeleteObject(t, c, instanceInvalidRego)) - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { ct := &v1beta1.ConstraintTemplate{} @@ -484,7 +436,7 @@ func TestReconcile(t *testing.T) { t.Fatal(err) } - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { resp, err := opaClient.Review(ctx, req) @@ -508,7 +460,7 @@ func TestReconcile_DeleteConstraintResources(t *testing.T) { logger.Info("Running test: Cancel the expectations when constraint gets deleted") // Setup the Manager - mgr, wm := setupManager(t) + mgr, wm := testutils.SetupManager(t, cfg) c := testclient.NewRetryClient(mgr.GetClient()) // start manager that will start tracker and controller @@ -573,13 +525,13 @@ violation[{"msg": "denied!"}] { // creating the gatekeeper-system namespace is necessary because that's where // status resources live by default - err = createGatekeeperNamespace(mgr.GetConfig()) + err = testutils.CreateGatekeeperNamespace(mgr.GetConfig()) if err != nil { t.Fatal(err) } // Set up tracker - tracker, err := readiness.SetupTrackerNoReadyz(mgr, false, false) + tracker, err := readiness.SetupTrackerNoReadyz(mgr, false, false, false) if err != nil { t.Fatal(err) } @@ -622,7 +574,7 @@ violation[{"msg": "denied!"}] { t.Fatalf("unexpected tracker, got %T", ot) } // ensure that expectations are set for the constraint gvk - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { gotExpected := tr.IsExpecting(gvk, types.NamespacedName{Name: "denyallconstraint"}) @@ -648,7 +600,7 @@ violation[{"msg": "denied!"}] { } // Check readiness tracker is satisfied post-reconcile - err = retry.OnError(constantRetry, func(err error) bool { + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { satisfied := tracker.For(gvk).Satisfied() @@ -663,7 +615,7 @@ violation[{"msg": "denied!"}] { } func constraintEnforced(ctx context.Context, c client.Client, suffix string) error { - return retry.OnError(constantRetry, func(err error) bool { + return retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { cstr := newDenyAllCstr(suffix) @@ -778,7 +730,7 @@ func applyCRD(ctx context.Context, client client.Client, gvk schema.GroupVersion u := &unstructured.UnstructuredList{} u.SetGroupVersionKind(gvk) - return retry.OnError(constantRetry, func(err error) bool { + return retry.OnError(testutils.ConstantRetry, func(err error) bool { return true }, func() error { if ctx.Err() != nil { @@ -788,99 +740,7 @@ func applyCRD(ctx context.Context, client client.Client, gvk schema.GroupVersion }) } -// deleteObjectAndConfirm returns a callback which deletes obj from the passed -// Client. Does result in mutations to obj. The callback includes a cached copy -// of all information required to delete obj in the callback, so it is safe to -// mutate obj afterwards. Similarly - client.Delete mutates its input, but -// the callback does not call client.Delete on obj. Instead, it creates a -// single-purpose Unstructured for this purpose. Thus, obj is not mutated after -// the callback is run. -func deleteObjectAndConfirm(ctx context.Context, t *testing.T, c client.Client, obj client.Object) func() { - t.Helper() - - // Cache the identifying information from obj. We refer to this cached - // information in the callback, and not obj itself. - gvk := obj.GetObjectKind().GroupVersionKind() - namespace := obj.GetNamespace() - name := obj.GetName() - - if gvk.Empty() { - // We can't send a proper delete request with an Unstructured without - // filling in GVK. The alternative would be to require tests to construct - // a valid Scheme or provide a factory method for the type to delete - this - // is easier. - t.Fatalf("gvk for %v/%v %T is empty", - namespace, name, obj) - } - - return func() { - t.Helper() - - // Construct a single-use Unstructured to send the Delete request. - toDelete := makeUnstructured(gvk, namespace, name) - err := c.Delete(ctx, toDelete) - if apierrors.IsNotFound(err) { - return - } else if err != nil { - t.Fatal(err) - } - - err = retry.OnError(constantRetry, func(err error) bool { - return true - }, func() error { - // Construct a single-use Unstructured to send the Get request. It isn't - // safe to reuse Unstructureds for each retry as Get modifies its input. - toGet := makeUnstructured(gvk, namespace, name) - key := client.ObjectKey{Namespace: namespace, Name: name} - err2 := c.Get(ctx, key, toGet) - if apierrors.IsGone(err2) || apierrors.IsNotFound(err2) { - return nil - } - - // Marshal the currently-gotten object, so it can be printed in test - // failure output. - s, _ := json.MarshalIndent(toGet, "", " ") - return fmt.Errorf("found %v %v:\n%s", gvk, key, string(s)) - }) - - if err != nil { - t.Fatal(err) - } - } -} - // This interface is getting used by tests to check the private objects of objectTracker. type testExpectations interface { IsExpecting(gvk schema.GroupVersionKind, nsName types.NamespacedName) bool } - -// createThenCleanup creates obj in Client, and then registers obj to be deleted -// at the end of the test. The passed obj is safely deepcopied before being -// passed to client.Create, so it is not mutated by this call. -func createThenCleanup(ctx context.Context, t *testing.T, c client.Client, obj client.Object) { - t.Helper() - cpy := obj.DeepCopyObject() - cpyObj, ok := cpy.(client.Object) - if !ok { - t.Fatalf("got obj.DeepCopyObject() type = %T, want %T", cpy, client.Object(nil)) - } - - err := c.Create(ctx, cpyObj) - if err != nil { - t.Fatal(err) - } - - // It is unnecessary to deepcopy obj as deleteObjectAndConfirm does not pass - // obj to any Client calls. - t.Cleanup(deleteObjectAndConfirm(ctx, t, c, obj)) -} - -func makeUnstructured(gvk schema.GroupVersionKind, namespace, name string) *unstructured.Unstructured { - u := &unstructured.Unstructured{ - Object: make(map[string]interface{}), - } - u.SetGroupVersionKind(gvk) - u.SetNamespace(namespace) - u.SetName(name) - return u -} diff --git a/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_suite_test.go b/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_suite_test.go index 64ea69a73ef..8fb12e0d951 100644 --- a/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_suite_test.go +++ b/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_suite_test.go @@ -16,65 +16,14 @@ limitations under the License. package constrainttemplatestatus_test import ( - "context" - stdlog "log" - "os" - "path/filepath" "testing" - "github.com/open-policy-agent/gatekeeper/apis" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" + "github.com/open-policy-agent/gatekeeper/test/testutils" "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/envtest" ) var cfg *rest.Config func TestMain(m *testing.M) { - var err error - - t := &envtest.Environment{ - CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "..", "vendor", "github.com", "open-policy-agent", "frameworks", "constraint", "deploy", "crds.yaml"), - filepath.Join("..", "..", "..", "config", "crd", "bases"), - }, - ErrorIfCRDPathMissing: true, - } - if err := apis.AddToScheme(scheme.Scheme); err != nil { - stdlog.Fatal(err) - } - - if cfg, err = t.Start(); err != nil { - stdlog.Fatal(err) - } - stdlog.Print("STARTED") - - code := m.Run() - if err = t.Stop(); err != nil { - stdlog.Printf("error while trying to stop server: %v", err) - } - os.Exit(code) -} - -// Bootstrap the gatekeeper-system namespace for use in tests. -func createGatekeeperNamespace(cfg *rest.Config) error { - c, err := client.New(cfg, client.Options{}) - if err != nil { - return err - } - - // Create gatekeeper namespace - ns := &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gatekeeper-system", - }, - } - - ctx := context.Background() - _, err = controllerutil.CreateOrUpdate(ctx, c, ns, func() error { return nil }) - return err + testutils.StartControlPlane(m, &cfg, 3) } diff --git a/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_test.go b/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_test.go index d7618b95243..dbb86abb500 100644 --- a/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_test.go +++ b/pkg/controller/constrainttemplatestatus/constrainttemplatestatus_controller_test.go @@ -108,7 +108,7 @@ violation[{"msg": "denied!"}] { // creating the gatekeeper-system namespace is necessary because that's where // status resources live by default - if err := createGatekeeperNamespace(mgr.GetConfig()); err != nil { + if err := testutils.CreateGatekeeperNamespace(mgr.GetConfig()); err != nil { t.Fatalf("want createGatekeeperNamespace(mgr.GetConfig()) error = nil, got %v", err) } @@ -126,7 +126,7 @@ violation[{"msg": "denied!"}] { testutils.Setenv(t, "POD_NAME", "no-pod") cs := watch.NewSwitch() - tracker, err := readiness.SetupTracker(mgr, false, false) + tracker, err := readiness.SetupTracker(mgr, false, false, false) if err != nil { t.Fatal(err) } diff --git a/pkg/controller/expansion/expansion_controller.go b/pkg/controller/expansion/expansion_controller.go index 6a1d0cf6aa0..be462937a65 100644 --- a/pkg/controller/expansion/expansion_controller.go +++ b/pkg/controller/expansion/expansion_controller.go @@ -2,18 +2,24 @@ package expansion import ( "context" - "flag" + "fmt" constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client" "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" "github.com/open-policy-agent/gatekeeper/apis/expansion/unversioned" "github.com/open-policy-agent/gatekeeper/apis/expansion/v1alpha1" + "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" + statusv1beta1 "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" "github.com/open-policy-agent/gatekeeper/pkg/expansion" "github.com/open-policy-agent/gatekeeper/pkg/logging" + "github.com/open-policy-agent/gatekeeper/pkg/metrics" "github.com/open-policy-agent/gatekeeper/pkg/mutation" "github.com/open-policy-agent/gatekeeper/pkg/readiness" + "github.com/open-policy-agent/gatekeeper/pkg/util" "github.com/open-policy-agent/gatekeeper/pkg/watch" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -25,22 +31,21 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) -var ( - expansionEnabled = flag.Bool("enable-generator-resource-expansion", false, "(alpha) Enable the expansion of generator resources") - - log = logf.Log.WithName("controller").WithValues("kind", "ExpansionTemplate", logging.Process, "template_expansion_controller") -) +var log = logf.Log.WithName("controller").WithValues("kind", "ExpansionTemplate", logging.Process, "template_expansion_controller") type Adder struct { WatchManager *watch.Manager ExpansionSystem *expansion.System + Tracker *readiness.Tracker + // GetPod returns an instance of the currently running Gatekeeper pod + GetPod func(context.Context) (*corev1.Pod, error) } func (a *Adder) Add(mgr manager.Manager) error { - if !*expansionEnabled { + if !*expansion.ExpansionEnabled { return nil } - r := newReconciler(mgr, a.ExpansionSystem) + r := newReconciler(mgr, a.ExpansionSystem, a.GetPod, a.Tracker) return add(mgr, r) } @@ -50,7 +55,9 @@ func (a *Adder) InjectWatchManager(_ *watch.Manager) {} func (a *Adder) InjectControllerSwitch(_ *watch.ControllerSwitch) {} -func (a *Adder) InjectTracker(_ *readiness.Tracker) {} +func (a *Adder) InjectTracker(tracker *readiness.Tracker) { + a.Tracker = tracker +} func (a *Adder) InjectMutationSystem(_ *mutation.System) {} @@ -58,21 +65,32 @@ func (a *Adder) InjectExpansionSystem(expansionSystem *expansion.System) { a.ExpansionSystem = expansionSystem } +func (a *Adder) InjectGetPod(getPod func(ctx context.Context) (*corev1.Pod, error)) { + a.GetPod = getPod +} + func (a *Adder) InjectProviderCache(_ *externaldata.ProviderCache) {} type Reconciler struct { client.Client - system *expansion.System - scheme *runtime.Scheme - registry *etRegistry + system *expansion.System + scheme *runtime.Scheme + registry *etRegistry + statusClient client.StatusClient + tracker *readiness.Tracker + + getPod func(context.Context) (*corev1.Pod, error) } -func newReconciler(mgr manager.Manager, system *expansion.System) *Reconciler { +func newReconciler(mgr manager.Manager, system *expansion.System, getPod func(ctx context.Context) (*corev1.Pod, error), tracker *readiness.Tracker) *Reconciler { return &Reconciler{ - Client: mgr.GetClient(), - system: system, - scheme: mgr.GetScheme(), - registry: newRegistry(), + Client: mgr.GetClient(), + system: system, + scheme: mgr.GetScheme(), + registry: newRegistry(), + statusClient: mgr.GetClient(), + getPod: getPod, + tracker: tracker, } } @@ -88,11 +106,12 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { } func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + defer r.registry.report(ctx) log.Info("Reconcile", "request", request, "namespace", request.Namespace, "name", request.Name) deleted := false - te := &v1alpha1.ExpansionTemplate{} - err := r.Get(ctx, request.NamespacedName, te) + versionedET := &v1alpha1.ExpansionTemplate{} + err := r.Get(ctx, request.NamespacedName, versionedET) if err != nil { if !errors.IsNotFound(err) { return reconcile.Result{}, err @@ -100,34 +119,113 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) ( deleted = true } - unversionedTE := &unversioned.ExpansionTemplate{} - if err := r.scheme.Convert(te, unversionedTE, nil); err != nil { + et := &unversioned.ExpansionTemplate{} + if err := r.scheme.Convert(versionedET, et, nil); err != nil { return reconcile.Result{}, err } - nsName := types.NamespacedName{ - Namespace: unversionedTE.GetNamespace(), - Name: unversionedTE.GetName(), - } + if deleted { - // unversionedTE will be an empty struct. We set the metadata name, which is + // et will be an empty struct. We set the metadata name, which is // used as a key to delete it from the expansion system - unversionedTE.ObjectMeta.Name = request.Name - if err := r.system.RemoveTemplate(unversionedTE); err != nil { + et.Name = request.Name + if err := r.system.RemoveTemplate(et); err != nil { + r.getTracker().TryCancelExpect(versionedET) return reconcile.Result{}, err } - log.Info("removed template expansion", "template name", unversionedTE.ObjectMeta.Name) - r.registry.remove(nsName) + log.Info("removed expansion template", "template name", et.GetName()) + r.registry.remove(request.NamespacedName) + r.getTracker().CancelExpect(versionedET) + return reconcile.Result{}, r.deleteStatus(ctx, request.NamespacedName.Name) + } + + upsertErr := r.system.UpsertTemplate(et) + if upsertErr == nil { + log.Info("[readiness] observed ExpansionTemplate", "template name", et.GetName()) + r.getTracker().Observe(versionedET) + r.registry.add(request.NamespacedName, metrics.ActiveStatus) } else { - if err := r.system.UpsertTemplate(unversionedTE); err != nil { - return reconcile.Result{}, err + r.getTracker().TryCancelExpect(versionedET) + r.registry.add(request.NamespacedName, metrics.ErrorStatus) + log.Error(upsertErr, "upserting template", "template_name", et.GetName()) + } + + return reconcile.Result{}, r.updateOrCreatePodStatus(ctx, et, upsertErr) +} + +func (r *Reconciler) deleteStatus(ctx context.Context, etName string) error { + status := &v1beta1.ExpansionTemplatePodStatus{} + pod, err := r.getPod(ctx) + if err != nil { + return fmt.Errorf("getting reconciler pod: %w", err) + } + sName, err := v1beta1.KeyForExpansionTemplate(pod.Name, etName) + if err != nil { + return fmt.Errorf("getting key for expansiontemplate: %w", err) + } + status.SetName(sName) + status.SetNamespace(util.GetNamespace()) + if err := r.Delete(ctx, status); err != nil && !apierrors.IsNotFound(err) { + return err + } + return nil +} + +func (r *Reconciler) updateOrCreatePodStatus(ctx context.Context, et *unversioned.ExpansionTemplate, etErr error) error { + pod, err := r.getPod(ctx) + if err != nil { + return fmt.Errorf("getting reconciler pod: %w", err) + } + + // Check if it exists already + sNS := pod.Namespace + sName, err := v1beta1.KeyForExpansionTemplate(pod.Name, et.GetName()) + if err != nil { + return fmt.Errorf("getting key for expansiontemplate: %w", err) + } + shouldCreate := true + status := &v1beta1.ExpansionTemplatePodStatus{} + + err = r.Get(ctx, types.NamespacedName{Namespace: sNS, Name: sName}, status) + switch { + case err == nil: + shouldCreate = false + case apierrors.IsNotFound(err): + if status, err = r.newETStatus(pod, et); err != nil { + return fmt.Errorf("creating new expansiontemplate status: %w", err) } - log.Info("upserted template expansion", "template name", unversionedTE.ObjectMeta.Name) - r.registry.add(nsName) + default: + return fmt.Errorf("getting expansion status in name %s, namespace %s: %w", et.GetName(), et.GetNamespace(), err) } - if err := r.registry.report(ctx); err != nil { - log.Error(err, "error reporting template expansion metrics", "namespacedName", nsName) + setStatusError(status, etErr) + status.Status.ObservedGeneration = et.GetGeneration() + + if shouldCreate { + return r.Create(ctx, status) + } + return r.Update(ctx, status) +} + +func (r *Reconciler) newETStatus(pod *corev1.Pod, et *unversioned.ExpansionTemplate) (*v1beta1.ExpansionTemplatePodStatus, error) { + status, err := statusv1beta1.NewExpansionTemplateStatusForPod(pod, et.GetName(), r.scheme) + if err != nil { + return nil, fmt.Errorf("creating status for pod: %w", err) + } + status.Status.TemplateUID = et.GetUID() + + return status, nil +} + +func (r *Reconciler) getTracker() readiness.Expectations { + return r.tracker.For(v1alpha1.GroupVersion.WithKind("ExpansionTemplate")) +} + +func setStatusError(status *v1beta1.ExpansionTemplatePodStatus, etErr error) { + if etErr == nil { + status.Status.Errors = nil + return } - return reconcile.Result{}, nil + e := &v1beta1.ExpansionTemplateError{Message: etErr.Error()} + status.Status.Errors = append(status.Status.Errors, e) } diff --git a/pkg/controller/expansion/expansion_controller_test.go b/pkg/controller/expansion/expansion_controller_test.go new file mode 100644 index 00000000000..802030dc40d --- /dev/null +++ b/pkg/controller/expansion/expansion_controller_test.go @@ -0,0 +1,203 @@ +package expansion + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/open-policy-agent/gatekeeper/apis/expansion/v1alpha1" + statusv1beta1 "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" + "github.com/open-policy-agent/gatekeeper/pkg/expansion" + "github.com/open-policy-agent/gatekeeper/pkg/fakes" + "github.com/open-policy-agent/gatekeeper/pkg/mutation" + "github.com/open-policy-agent/gatekeeper/pkg/mutation/match" + "github.com/open-policy-agent/gatekeeper/pkg/readiness" + testclient "github.com/open-policy-agent/gatekeeper/test/clients" + "github.com/open-policy-agent/gatekeeper/test/testutils" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/util/retry" +) + +var cfg *rest.Config + +func TestMain(m *testing.M) { + testutils.StartControlPlane(m, &cfg, 3) +} + +func TestReconcile(t *testing.T) { + // Uncommenting the below enables logging of K8s internals like watch. + // fs := flag.NewFlagSet("", flag.PanicOnError) + // klog.InitFlags(fs) + // fs.Parse([]string{"--alsologtostderr", "-v=10"}) + // klog.SetOutput(os.Stderr) + + mgr, _ := testutils.SetupManager(t, cfg) + c := testclient.NewRetryClient(mgr.GetClient()) + + // creating the gatekeeper-system namespace is necessary because that's where + // status resources live by default + err := testutils.CreateGatekeeperNamespace(mgr.GetConfig()) + if err != nil { + t.Fatal(err) + } + + mutSystem := mutation.NewSystem(mutation.SystemOpts{}) + expSystem := expansion.NewSystem(mutSystem) + + testutils.Setenv(t, "POD_NAME", "no-pod") + + tracker, err := readiness.SetupTracker(mgr, false, false, true) + if err != nil { + t.Fatal(err) + } + + pod := fakes.Pod( + fakes.WithNamespace("gatekeeper-system"), + fakes.WithName("no-pod"), + ) + + r := newReconciler(mgr, expSystem, func(context.Context) (*corev1.Pod, error) { return pod, nil }, tracker) + if err != nil { + t.Fatal(err) + } + + err = add(mgr, r) + if err != nil { + t.Fatal(err) + } + + ctx := context.Background() + testutils.StartManager(ctx, t, mgr) + + t.Run("creating ET creates status obj, deleting ET deletes status", func(t *testing.T) { + t.Log("running test: creating ET creates ETPodStatus, deleting ET deletes status") + + etName := "default-et" + et := newET(etName) + + sName, err := statusv1beta1.KeyForExpansionTemplate("no-pod", etName) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(testutils.DeleteObjectAndConfirm(ctx, t, c, et)) + testutils.CreateThenCleanup(ctx, t, c, et) + + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { + return true + }, func() error { + // First, get the ET + et := &v1alpha1.ExpansionTemplate{} + nsName := types.NamespacedName{Name: etName} + if err := c.Get(ctx, nsName, et); err != nil { + return err + } + if err != nil { + return fmt.Errorf("error fetching ET: %w", err) + } + + // Get the ETPodStatus + status := &statusv1beta1.ExpansionTemplatePodStatus{} + nsName = types.NamespacedName{ + Name: sName, + Namespace: "gatekeeper-system", + } + if err := c.Get(ctx, nsName, status); err != nil { + return err + } + if err != nil { + return fmt.Errorf("error fetching ET status: %w", err) + } + if status.Status.TemplateUID == et.GetUID() { + return nil + } + return fmt.Errorf("ExpansionTemplatePodStatus.Status.TemplateUID %q does not match ExpansionTemplate.GetUID() %q", status.Status.TemplateUID, et.GetUID()) + }) + if err != nil { + t.Fatal(err) + } + + if err := c.Delete(ctx, et); err != nil { + t.Fatalf("error deleting ET: %s", err) + } + + err = retry.OnError(testutils.ConstantRetry, func(err error) bool { + return true + }, func() error { + // Get the ETPodStatus + status := &statusv1beta1.ExpansionTemplatePodStatus{} + nsName := types.NamespacedName{ + Name: sName, + Namespace: "gatekeeper-system", + } + if err := c.Get(ctx, nsName, status); err != nil && apierrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("expected IsNotFound when fetching status, but got: %w", err) + }) + if err != nil { + t.Fatal(err) + } + }) +} + +func TestAddStatusError(t *testing.T) { + tests := []struct { + name string + inputStatus statusv1beta1.ExpansionTemplatePodStatus + etErr error + wantStatus statusv1beta1.ExpansionTemplatePodStatus + }{ + { + name: "no err", + inputStatus: statusv1beta1.ExpansionTemplatePodStatus{}, + etErr: nil, + wantStatus: statusv1beta1.ExpansionTemplatePodStatus{Status: statusv1beta1.ExpansionTemplatePodStatusStatus{}}, + }, + { + name: "with err", + inputStatus: statusv1beta1.ExpansionTemplatePodStatus{}, + etErr: errors.New("big problem"), + wantStatus: statusv1beta1.ExpansionTemplatePodStatus{ + Status: statusv1beta1.ExpansionTemplatePodStatusStatus{ + Errors: []*statusv1beta1.ExpansionTemplateError{{Message: "big problem"}}, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + setStatusError(&tc.inputStatus, tc.etErr) + if diff := cmp.Diff(tc.inputStatus, tc.wantStatus); diff != "" { + t.Errorf("got: %v\nwant: %v\ndiff: %s", tc.inputStatus, tc.wantStatus, diff) + } + }) + } +} + +func newET(name string) *v1alpha1.ExpansionTemplate { + et := &v1alpha1.ExpansionTemplate{ + ObjectMeta: v1.ObjectMeta{Name: name}, + Spec: v1alpha1.ExpansionTemplateSpec{ + ApplyTo: []match.ApplyTo{{ + Groups: []string{"apps"}, + Kinds: []string{"Deployment"}, + Versions: []string{"v1"}, + }}, + TemplateSource: "spec.template", + GeneratedGVK: v1alpha1.GeneratedGVK{ + Kind: "Pod", + Version: "v1", + }, + }, + } + et.SetGroupVersionKind(v1alpha1.GroupVersion.WithKind("ExpansionTemplate")) + return et +} diff --git a/pkg/controller/expansion/stats_reporter.go b/pkg/controller/expansion/stats_reporter.go index 91bf1c45076..a6695214d71 100644 --- a/pkg/controller/expansion/stats_reporter.go +++ b/pkg/controller/expansion/stats_reporter.go @@ -6,6 +6,7 @@ import ( "github.com/open-policy-agent/gatekeeper/pkg/metrics" "go.opencensus.io/stats" "go.opencensus.io/stats/view" + "go.opencensus.io/tag" "k8s.io/apimachinery/pkg/types" ) @@ -15,7 +16,8 @@ const ( ) var ( - etM = stats.Int64(etMetricName, etDesc, stats.UnitDimensionless) + etM = stats.Int64(etMetricName, etDesc, stats.UnitDimensionless) + statusKey = tag.MustNewKey("status") views = []*view.View{ { @@ -23,6 +25,7 @@ var ( Measure: etM, Description: etDesc, Aggregation: view.LastValue(), + TagKeys: []tag.Key{statusKey}, }, } ) @@ -38,33 +41,50 @@ func register() error { } func newRegistry() *etRegistry { - return &etRegistry{cache: make(map[types.NamespacedName]bool)} + return &etRegistry{cache: make(map[types.NamespacedName]metrics.Status)} } type etRegistry struct { - cache map[types.NamespacedName]bool + cache map[types.NamespacedName]metrics.Status dirty bool } -func (r *etRegistry) add(key types.NamespacedName) { - r.cache[key] = true +func (r *etRegistry) add(key types.NamespacedName, status metrics.Status) { + v, ok := r.cache[key] + if ok && v == status { + return + } + r.cache[key] = status r.dirty = true } func (r *etRegistry) remove(key types.NamespacedName) { + if _, exists := r.cache[key]; !exists { + return + } delete(r.cache, key) r.dirty = true } -func (r *etRegistry) report(ctx context.Context) error { +func (r *etRegistry) report(ctx context.Context) { if !r.dirty { - return nil + return } - if err := metrics.Record(ctx, etM.M(int64(len(r.cache)))); err != nil { - r.dirty = false - return err + totals := make(map[metrics.Status]int64) + for _, status := range r.cache { + totals[status]++ } - return nil + for _, s := range metrics.AllStatuses { + ctx, err := tag.New(ctx, tag.Insert(statusKey, string(s))) + if err != nil { + log.Error(err, "failed to create status tag for expansion templates") + continue + } + err = metrics.Record(ctx, etM.M(totals[s])) + if err != nil { + log.Error(err, "failed to record total expansion templates") + } + } } diff --git a/pkg/controller/expansionstatus/expansionstatus_controller.go b/pkg/controller/expansionstatus/expansionstatus_controller.go new file mode 100644 index 00000000000..b22489b4810 --- /dev/null +++ b/pkg/controller/expansionstatus/expansionstatus_controller.go @@ -0,0 +1,213 @@ +/* + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package expansionstatus + +import ( + "context" + "fmt" + "sort" + + "github.com/go-logr/logr" + constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client" + "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" + expansionv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/expansion/v1alpha1" + "github.com/open-policy-agent/gatekeeper/apis/status/v1beta1" + "github.com/open-policy-agent/gatekeeper/pkg/expansion" + "github.com/open-policy-agent/gatekeeper/pkg/logging" + "github.com/open-policy-agent/gatekeeper/pkg/mutation" + "github.com/open-policy-agent/gatekeeper/pkg/readiness" + "github.com/open-policy-agent/gatekeeper/pkg/util" + "github.com/open-policy-agent/gatekeeper/pkg/watch" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +var log = logf.Log.WithName("controller").WithValues(logging.Process, "expansion_template_status_controller") + +type Adder struct { + Opa *constraintclient.Client + WatchManager *watch.Manager +} + +func (a *Adder) InjectOpa(o *constraintclient.Client) {} + +func (a *Adder) InjectWatchManager(w *watch.Manager) {} + +func (a *Adder) InjectControllerSwitch(cs *watch.ControllerSwitch) {} + +func (a *Adder) InjectTracker(t *readiness.Tracker) {} + +func (a *Adder) InjectMutationSystem(mutationSystem *mutation.System) {} + +func (a *Adder) InjectExpansionSystem(expansionSystem *expansion.System) {} + +func (a *Adder) InjectProviderCache(providerCache *externaldata.ProviderCache) {} + +// Add creates a new Constraint Status Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func (a *Adder) Add(mgr manager.Manager) error { + if !*expansion.ExpansionEnabled { + return nil + } + r := newReconciler(mgr) + return add(mgr, r) +} + +// newReconciler returns a new reconcile.Reconciler. +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileExpansionStatus{ + // Separate reader and writer because manager's default client bypasses the cache for unstructured resources. + writer: mgr.GetClient(), + statusClient: mgr.GetClient(), + reader: mgr.GetCache(), + + scheme: mgr.GetScheme(), + log: log, + } +} + +// PodStatusToExpansionTemplateMapper correlates a ExpansionTemplatePodStatus with its corresponding expansion template. +// `selfOnly` tells the mapper to only map statuses corresponding to the current pod. +func PodStatusToExpansionTemplateMapper(selfOnly bool) handler.MapFunc { + return func(obj client.Object) []reconcile.Request { + labels := obj.GetLabels() + name, ok := labels[v1beta1.ExpansionTemplateNameLabel] + if !ok { + log.Error(fmt.Errorf("expansion template status resource with no mapping label: %s", obj.GetName()), "missing label while attempting to map a expansion template status resource") + return nil + } + if selfOnly { + pod, ok := labels[v1beta1.PodLabel] + if !ok { + log.Error(fmt.Errorf("expansion template status resource with no pod label: %s", obj.GetName()), "missing label while attempting to map a expansion template status resource") + } + // Do not attempt to reconcile the resource when other pods have changed their status + if pod != util.GetPodName() { + return nil + } + } + return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: name}}} + } +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler. +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("expansion-template-status-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to ExpansionTemplateStatus + err = c.Watch( + &source.Kind{Type: &v1beta1.ExpansionTemplatePodStatus{}}, + handler.EnqueueRequestsFromMapFunc(PodStatusToExpansionTemplateMapper(false)), + ) + if err != nil { + return err + } + + // Watch for changes to ExpansionTemplate + err = c.Watch(&source.Kind{Type: &expansionv1alpha1.ExpansionTemplate{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + return nil +} + +var _ reconcile.Reconciler = &ReconcileExpansionStatus{} + +// ReconcileExpansionStatus reconciles an arbitrary constraint object described by Kind. +type ReconcileExpansionStatus struct { + reader client.Reader + writer client.Writer + statusClient client.StatusClient + + scheme *runtime.Scheme + log logr.Logger +} + +// +kubebuilder:rbac:groups=expansion.gatekeeper.sh,resources=*,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=status.gatekeeper.sh,resources=*,verbs=get;list;watch;create;update;patch;delete + +// Reconcile reads that state of the cluster for a constraint object and makes changes based on the state read +// and what is in the constraint.Spec. +func (r *ReconcileExpansionStatus) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + et := &expansionv1alpha1.ExpansionTemplate{} + err := r.reader.Get(ctx, request.NamespacedName, et) + if err != nil { + // If the ExpansionTemplate does not exist then we are done + if errors.IsNotFound(err) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + + sObjs := &v1beta1.ExpansionTemplatePodStatusList{} + if err := r.reader.List( + ctx, + sObjs, + client.MatchingLabels{v1beta1.ExpansionTemplateNameLabel: request.Name}, + client.InNamespace(util.GetNamespace()), + ); err != nil { + return reconcile.Result{}, err + } + statusObjs := make(sortableStatuses, len(sObjs.Items)) + copy(statusObjs, sObjs.Items) + sort.Sort(statusObjs) + + var s []v1beta1.ExpansionTemplatePodStatusStatus + // created is true if at least one Pod hasn't reported any errors + + for i := range statusObjs { + // Don't report status if it's not for the correct object. This can happen + // if a watch gets interrupted, causing the constraint status to be deleted + // out from underneath it + if statusObjs[i].Status.TemplateUID != et.GetUID() { + continue + } + s = append(s, statusObjs[i].Status) + } + + et.Status.ByPod = s + + if err := r.statusClient.Status().Update(ctx, et); err != nil { + return reconcile.Result{Requeue: true}, nil + } + return reconcile.Result{}, nil +} + +type sortableStatuses []v1beta1.ExpansionTemplatePodStatus + +func (s sortableStatuses) Len() int { + return len(s) +} + +func (s sortableStatuses) Less(i, j int) bool { + return s[i].Status.ID < s[j].Status.ID +} + +func (s sortableStatuses) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} diff --git a/pkg/controller/externaldata/externaldata_controller_suite_test.go b/pkg/controller/externaldata/externaldata_controller_suite_test.go index 35169bc3357..d04987e6f7e 100644 --- a/pkg/controller/externaldata/externaldata_controller_suite_test.go +++ b/pkg/controller/externaldata/externaldata_controller_suite_test.go @@ -17,44 +17,17 @@ package externaldata import ( "context" - stdlog "log" - "os" - "path/filepath" "testing" - "github.com/open-policy-agent/gatekeeper/apis" - "k8s.io/client-go/kubernetes/scheme" + "github.com/open-policy-agent/gatekeeper/test/testutils" "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) var cfg *rest.Config func TestMain(m *testing.M) { - var err error - - t := &envtest.Environment{ - CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "..", "vendor", "github.com", "open-policy-agent", "frameworks", "constraint", "deploy", "crds.yaml"), - filepath.Join("..", "..", "..", "config", "crd", "bases"), - }, - ErrorIfCRDPathMissing: true, - } - if err := apis.AddToScheme(scheme.Scheme); err != nil { - stdlog.Fatal(err) - } - - if cfg, err = t.Start(); err != nil { - stdlog.Fatal(err) - } - stdlog.Print("STARTED") - - code := m.Run() - if err = t.Stop(); err != nil { - stdlog.Printf("error while trying to stop server: %v", err) - } - os.Exit(code) + testutils.StartControlPlane(m, &cfg, 3) } // SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and diff --git a/pkg/controller/externaldata/externaldata_controller_test.go b/pkg/controller/externaldata/externaldata_controller_test.go index 717d6c813b3..f16adbefd2c 100644 --- a/pkg/controller/externaldata/externaldata_controller_test.go +++ b/pkg/controller/externaldata/externaldata_controller_test.go @@ -96,7 +96,7 @@ func TestReconcile(t *testing.T) { } cs := watch.NewSwitch() - tracker, err := readiness.SetupTracker(mgr, false, true) + tracker, err := readiness.SetupTracker(mgr, false, true, false) if err != nil { t.Fatal(err) } diff --git a/pkg/controller/mutators/core/controller_suite_test.go b/pkg/controller/mutators/core/controller_suite_test.go index 510170ad405..9e1314004d5 100644 --- a/pkg/controller/mutators/core/controller_suite_test.go +++ b/pkg/controller/mutators/core/controller_suite_test.go @@ -16,19 +16,14 @@ limitations under the License. package core import ( - "context" "log" "os" "path/filepath" "testing" "github.com/open-policy-agent/gatekeeper/apis" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/envtest" ) @@ -57,22 +52,3 @@ func TestMain(m *testing.M) { } os.Exit(code) } - -// Bootstrap the gatekeeper-system namespace for use in tests. -func createGatekeeperNamespace(cfg *rest.Config) error { - c, err := client.New(cfg, client.Options{}) - if err != nil { - return err - } - - // Create gatekeeper namespace - ns := &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gatekeeper-system", - }, - } - - ctx := context.Background() - _, err = controllerutil.CreateOrUpdate(ctx, c, ns, func() error { return nil }) - return err -} diff --git a/pkg/controller/mutators/core/controller_test.go b/pkg/controller/mutators/core/controller_test.go index 6e9985715f8..3a86e12dfa9 100644 --- a/pkg/controller/mutators/core/controller_test.go +++ b/pkg/controller/mutators/core/controller_test.go @@ -117,13 +117,13 @@ func TestReconcile(t *testing.T) { // creating the gatekeeper-system namespace is necessary because that's where // status resources live by default - if err := createGatekeeperNamespace(mgr.GetConfig()); err != nil { + if err := testutils.CreateGatekeeperNamespace(mgr.GetConfig()); err != nil { t.Fatalf("want createGatekeeperNamespace(mgr.GetConfig()) error = nil, got %v", err) } mSys := mutation.NewSystem(mutation.SystemOpts{}) - tracker, err := readiness.SetupTracker(mgr, true, false) + tracker, err := readiness.SetupTracker(mgr, true, false, false) if err != nil { t.Fatal(err) } diff --git a/pkg/expansion/system.go b/pkg/expansion/system.go index 0844f45c108..6984b247b96 100644 --- a/pkg/expansion/system.go +++ b/pkg/expansion/system.go @@ -2,6 +2,7 @@ package expansion import ( "encoding/json" + "flag" "fmt" "strings" "sync" @@ -14,6 +15,12 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) +var ExpansionEnabled *bool + +func init() { + ExpansionEnabled = flag.Bool("enable-generator-resource-expansion", false, "(alpha) Enable the expansion of generator resources") +} + type System struct { lock sync.RWMutex templates map[string]*expansionunversioned.ExpansionTemplate @@ -34,7 +41,7 @@ func (s *System) UpsertTemplate(template *expansionunversioned.ExpansionTemplate s.lock.Lock() defer s.lock.Unlock() - if err := validateTemplate(template); err != nil { + if err := ValidateTemplate(template); err != nil { return err } @@ -55,7 +62,7 @@ func (s *System) RemoveTemplate(template *expansionunversioned.ExpansionTemplate return nil } -func validateTemplate(template *expansionunversioned.ExpansionTemplate) error { +func ValidateTemplate(template *expansionunversioned.ExpansionTemplate) error { k := keyForTemplate(template) if k == "" { return fmt.Errorf("ExpansionTemplate has empty name field") diff --git a/pkg/readiness/ready_tracker.go b/pkg/readiness/ready_tracker.go index 07c89a51b4e..b35dc972588 100644 --- a/pkg/readiness/ready_tracker.go +++ b/pkg/readiness/ready_tracker.go @@ -26,6 +26,7 @@ import ( "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1beta1" "github.com/open-policy-agent/frameworks/constraint/pkg/core/templates" configv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/config/v1alpha1" + expansionv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/expansion/v1alpha1" mutationv1 "github.com/open-policy-agent/gatekeeper/apis/mutations/v1" mutationsv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/mutations/v1alpha1" "github.com/open-policy-agent/gatekeeper/pkg/keys" @@ -67,6 +68,7 @@ type Tracker struct { modifySet *objectTracker assignImage *objectTracker externalDataProvider *objectTracker + expansions *objectTracker constraints *trackerMap data *trackerMap @@ -75,14 +77,15 @@ type Tracker struct { statsEnabled syncutil.SyncBool mutationEnabled bool externalDataEnabled bool + expansionEnabled bool } // NewTracker creates a new Tracker and initializes the internal trackers. -func NewTracker(lister Lister, mutationEnabled bool, externalDataEnabled bool) *Tracker { - return newTracker(lister, mutationEnabled, externalDataEnabled, nil) +func NewTracker(lister Lister, mutationEnabled, externalDataEnabled, expansionEnabled bool) *Tracker { + return newTracker(lister, mutationEnabled, externalDataEnabled, expansionEnabled, nil) } -func newTracker(lister Lister, mutationEnabled bool, externalDataEnabled bool, fn objDataFactory) *Tracker { +func newTracker(lister Lister, mutationEnabled, externalDataEnabled, expansionEnabled bool, fn objDataFactory) *Tracker { tracker := Tracker{ lister: lister, templates: newObjTracker(v1beta1.SchemeGroupVersion.WithKind("ConstraintTemplate"), fn), @@ -94,6 +97,7 @@ func newTracker(lister Lister, mutationEnabled bool, externalDataEnabled bool, f mutationEnabled: mutationEnabled, externalDataEnabled: externalDataEnabled, + expansionEnabled: expansionEnabled, } if mutationEnabled { tracker.assignMetadata = newObjTracker(mutationv1.GroupVersion.WithKind("AssignMetadata"), fn) @@ -104,6 +108,9 @@ func newTracker(lister Lister, mutationEnabled bool, externalDataEnabled bool, f if externalDataEnabled { tracker.externalDataProvider = newObjTracker(externaldatav1beta1.SchemeGroupVersion.WithKind("Provider"), fn) } + if expansionEnabled { + tracker.expansions = newObjTracker(expansionv1alpha1.GroupVersion.WithKind("ExpansionTemplate"), fn) + } return &tracker } @@ -153,6 +160,11 @@ func (t *Tracker) For(gvk schema.GroupVersionKind) Expectations { return t.assignImage } return noopExpectations{} + case gvk.GroupVersion() == expansionv1alpha1.GroupVersion && gvk.Kind == "ExpansionTemplate": + if t.expansionEnabled { + return t.expansions + } + return noopExpectations{} } // Avoid new constraint trackers after templates have been populated. @@ -232,6 +244,13 @@ func (t *Tracker) Satisfied() bool { log.V(1).Info("all expectations satisfied", "tracker", "provider") } + if t.expansionEnabled { + if !t.expansions.Satisfied() { + return false + } + log.V(1).Info("all expectations satisfied", "tracker", "expansiontemplates") + } + if operations.HasValidationOperations() { if !t.templates.Satisfied() { return false @@ -294,6 +313,11 @@ func (t *Tracker) Run(ctx context.Context) error { return t.trackExternalDataProvider(gctx) }) } + if t.expansionEnabled { + grp.Go(func() error { + return t.trackExpansionTemplates(gctx) + }) + } if operations.HasValidationOperations() { grp.Go(func() error { return t.trackConstraintTemplates(gctx) @@ -567,6 +591,31 @@ func (t *Tracker) trackAssignImage(ctx context.Context) error { return nil } +func (t *Tracker) trackExpansionTemplates(ctx context.Context) error { + defer func() { + t.expansions.ExpectationsDone() + log.V(1).Info("ExpansionTemplate expectations populated") + _ = t.constraintTrackers.Wait() + }() + + if !t.expansionEnabled { + return nil + } + + expansionList := &expansionv1alpha1.ExpansionTemplateList{} + lister := retryLister(t.lister, retryAll) + if err := lister.List(ctx, expansionList); err != nil { + return fmt.Errorf("listing ExpansionTemplate: %w", err) + } + log.V(1).Info("setting expectations for ExpansionTemplate", "ExpansionTemplate Count", len(expansionList.Items)) + + for index := range expansionList.Items { + log.V(1).Info("expecting ExpansionTemplate", "name", expansionList.Items[index].GetName()) + t.expansions.Expect(&expansionList.Items[index]) + } + return nil +} + func (t *Tracker) trackExternalDataProvider(ctx context.Context) error { defer func() { t.externalDataProvider.ExpectationsDone() @@ -858,6 +907,9 @@ func (t *Tracker) statsPrinter(ctx context.Context) { if t.externalDataEnabled { logUnsatisfiedExternalDataProvider(t) } + if t.expansionEnabled { + logUnsatisfiedExpansions(t) + } } } @@ -885,6 +937,12 @@ func logUnsatisfiedAssignImage(t *Tracker) { } } +func logUnsatisfiedExpansions(t *Tracker) { + for _, et := range t.expansions.unsatisfied() { + log.Info("unsatisfied ExpansionTemplate", "name", et.namespacedName) + } +} + func logUnsatisfiedExternalDataProvider(t *Tracker) { for _, amKey := range t.externalDataProvider.unsatisfied() { log.Info("unsatisfied Provider", "name", amKey.namespacedName) diff --git a/pkg/readiness/ready_tracker_test.go b/pkg/readiness/ready_tracker_test.go index 4cc6771a015..40156ff101a 100644 --- a/pkg/readiness/ready_tracker_test.go +++ b/pkg/readiness/ready_tracker_test.go @@ -32,6 +32,7 @@ import ( frameworksexternaldata "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" "github.com/open-policy-agent/gatekeeper/pkg/controller" "github.com/open-policy-agent/gatekeeper/pkg/controller/config/process" + "github.com/open-policy-agent/gatekeeper/pkg/expansion" "github.com/open-policy-agent/gatekeeper/pkg/fakes" "github.com/open-policy-agent/gatekeeper/pkg/mutation" mutationtypes "github.com/open-policy-agent/gatekeeper/pkg/mutation/types" @@ -45,6 +46,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -108,9 +110,12 @@ func setupController( wm *watch.Manager, opa *constraintclient.Client, mutationSystem *mutation.System, + expansionSystem *expansion.System, providerCache *frameworksexternaldata.ProviderCache, ) error { - tracker, err := readiness.SetupTracker(mgr, mutationSystem != nil, providerCache != nil) + *expansion.ExpansionEnabled = expansionSystem != nil + + tracker, err := readiness.SetupTracker(mgr, mutationSystem != nil, providerCache != nil, expansionSystem != nil) if err != nil { return fmt.Errorf("setting up tracker: %w", err) } @@ -135,6 +140,7 @@ func setupController( GetPod: func(ctx context.Context) (*corev1.Pod, error) { return pod, nil }, ProcessExcluder: processExcluder, MutationSystem: mutationSystem, + ExpansionSystem: expansionSystem, ProviderCache: providerCache, WatchSet: watch.NewSet(), } @@ -158,9 +164,10 @@ func Test_AssignMetadata(t *testing.T) { opaClient := setupOpa(t) mutationSystem := mutation.NewSystem(mutation.SystemOpts{}) + expansionSystem := expansion.NewSystem(mutationSystem) providerCache := frameworksexternaldata.NewCache() - if err := setupController(mgr, wm, opaClient, mutationSystem, providerCache); err != nil { + if err := setupController(mgr, wm, opaClient, mutationSystem, expansionSystem, providerCache); err != nil { t.Fatalf("setupControllers: %v", err) } @@ -201,9 +208,10 @@ func Test_ModifySet(t *testing.T) { opaClient := setupOpa(t) mutationSystem := mutation.NewSystem(mutation.SystemOpts{}) + expansionSystem := expansion.NewSystem(mutationSystem) providerCache := frameworksexternaldata.NewCache() - if err := setupController(mgr, wm, opaClient, mutationSystem, providerCache); err != nil { + if err := setupController(mgr, wm, opaClient, mutationSystem, expansionSystem, providerCache); err != nil { t.Fatalf("setupControllers: %v", err) } @@ -242,9 +250,10 @@ func Test_AssignImage(t *testing.T) { opaClient := setupOpa(t) mutationSystem := mutation.NewSystem(mutation.SystemOpts{}) + expansionSystem := expansion.NewSystem(mutationSystem) providerCache := frameworksexternaldata.NewCache() - if err := setupController(mgr, wm, opaClient, mutationSystem, providerCache); err != nil { + if err := setupController(mgr, wm, opaClient, mutationSystem, expansionSystem, providerCache); err != nil { t.Fatalf("setupControllers: %v", err) } @@ -283,9 +292,10 @@ func Test_Assign(t *testing.T) { opaClient := setupOpa(t) mutationSystem := mutation.NewSystem(mutation.SystemOpts{}) + expansionSystem := expansion.NewSystem(mutationSystem) providerCache := frameworksexternaldata.NewCache() - if err := setupController(mgr, wm, opaClient, mutationSystem, providerCache); err != nil { + if err := setupController(mgr, wm, opaClient, mutationSystem, expansionSystem, providerCache); err != nil { t.Fatalf("setupControllers: %v", err) } @@ -308,6 +318,61 @@ func Test_Assign(t *testing.T) { } } +func Test_ExpansionTemplate(t *testing.T) { + g := gomega.NewWithT(t) + + testutils.Setenv(t, "POD_NAME", "no-pod") + + // Apply fixtures *before* the controllers are setup. + err := applyFixtures("testdata") + if err != nil { + t.Fatalf("applying fixtures: %v", err) + } + + // Wire up the rest. + mgr, wm := setupManager(t) + opaClient := setupOpa(t) + + mutationSystem := mutation.NewSystem(mutation.SystemOpts{}) + expansionSystem := expansion.NewSystem(mutationSystem) + providerCache := frameworksexternaldata.NewCache() + + if err := setupController(mgr, wm, opaClient, mutationSystem, expansionSystem, providerCache); err != nil { + t.Fatalf("setupControllers: %v", err) + } + + ctx := context.Background() + testutils.StartManager(ctx, t, mgr) + + g.Eventually(func() (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + return probeIsReady(ctx) + }, 20*time.Second, 1*time.Second).Should(gomega.BeTrue()) + + // Verify that the ExpansionTemplate is registered by expanding a demo deployment + // and checking that the resulting Pod is non-nil + deployment := makeDeployment("demo-deployment") + o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(deployment) + if err != nil { + panic(fmt.Errorf("error converting deployment to unstructured: %w", err)) + } + u := unstructured.Unstructured{Object: o} + m := mutationtypes.Mutable{ + Object: &u, + Namespace: testNS, + Username: "", + Source: "All", + } + res, err := expansionSystem.Expand(&m) + if err != nil { + panic(fmt.Errorf("error expanding: %w", err)) + } + if len(res) != 1 { + t.Fatal("expected generator to expand into 1 pod, but got 0 resultants") + } +} + func Test_Provider(t *testing.T) { g := gomega.NewWithT(t) @@ -331,6 +396,7 @@ func Test_Provider(t *testing.T) { wm, opaClient, mutation.NewSystem(mutation.SystemOpts{}), + nil, providerCache); err != nil { t.Fatalf("setupControllers: %v", err) } @@ -385,7 +451,7 @@ func Test_Tracker(t *testing.T) { opaClient := setupOpa(t) providerCache := frameworksexternaldata.NewCache() - if err := setupController(mgr, wm, opaClient, mutation.NewSystem(mutation.SystemOpts{}), providerCache); err != nil { + if err := setupController(mgr, wm, opaClient, mutation.NewSystem(mutation.SystemOpts{}), nil, providerCache); err != nil { t.Fatalf("setupControllers: %v", err) } @@ -483,7 +549,7 @@ func Test_Tracker_UnregisteredCachedData(t *testing.T) { opaClient := setupOpa(t) providerCache := frameworksexternaldata.NewCache() - if err := setupController(mgr, wm, opaClient, mutation.NewSystem(mutation.SystemOpts{}), providerCache); err != nil { + if err := setupController(mgr, wm, opaClient, mutation.NewSystem(mutation.SystemOpts{}), nil, providerCache); err != nil { t.Fatalf("setupControllers: %v", err) } @@ -523,7 +589,7 @@ func Test_CollectDeleted(t *testing.T) { lister: mgr.GetAPIReader(), namespace: "gatekeeper-system", } - tracker := readiness.NewTracker(lister, false, false) + tracker := readiness.NewTracker(lister, false, false, false) err = mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { return tracker.Run(ctx) })) diff --git a/pkg/readiness/ready_tracker_unit_test.go b/pkg/readiness/ready_tracker_unit_test.go index b732400f321..bfdc38fc1e5 100644 --- a/pkg/readiness/ready_tracker_unit_test.go +++ b/pkg/readiness/ready_tracker_unit_test.go @@ -72,7 +72,7 @@ func Test_ReadyTracker_TryCancelTemplate_No_Retries(t *testing.T) { g := gomega.NewWithT(t) l := dummyLister{} - rt := newTracker(l, false, false, func() objData { + rt := newTracker(l, false, false, false, func() objData { return objData{retries: 0} }) @@ -114,7 +114,7 @@ func Test_ReadyTracker_TryCancelTemplate_Retries(t *testing.T) { g := gomega.NewWithT(t) l := dummyLister{} - rt := newTracker(l, false, false, func() objData { + rt := newTracker(l, false, false, false, func() objData { return objData{retries: 2} }) diff --git a/pkg/readiness/setup.go b/pkg/readiness/setup.go index 10353c18f63..47e2b3721ce 100644 --- a/pkg/readiness/setup.go +++ b/pkg/readiness/setup.go @@ -25,8 +25,8 @@ import ( // SetupTracker sets up a readiness tracker and registers it to run under control of the // provided Manager object. // NOTE: Must be called _before_ the manager is started. -func SetupTracker(mgr manager.Manager, mutationEnabled bool, externalDataEnabled bool) (*Tracker, error) { - tracker, err := SetupTrackerNoReadyz(mgr, mutationEnabled, externalDataEnabled) +func SetupTracker(mgr manager.Manager, mutationEnabled, externalDataEnabled, expansionEnabled bool) (*Tracker, error) { + tracker, err := SetupTrackerNoReadyz(mgr, mutationEnabled, externalDataEnabled, expansionEnabled) if err != nil { return nil, err } @@ -40,8 +40,8 @@ func SetupTracker(mgr manager.Manager, mutationEnabled bool, externalDataEnabled // SetupTrackerNoReadyz sets up a readiness tracker and registers it to run under control of the // provided Manager object without instantiating /readyz (used for testing). -func SetupTrackerNoReadyz(mgr manager.Manager, mutationEnabled bool, externalDataEnabled bool) (*Tracker, error) { - tracker := NewTracker(mgr.GetAPIReader(), mutationEnabled, externalDataEnabled) +func SetupTrackerNoReadyz(mgr manager.Manager, mutationEnabled, externalDataEnabled, expansionEnabled bool) (*Tracker, error) { + tracker := NewTracker(mgr.GetAPIReader(), mutationEnabled, externalDataEnabled, expansionEnabled) err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { return tracker.Run(ctx) diff --git a/pkg/readiness/testdata/99-expansion-template.yaml b/pkg/readiness/testdata/99-expansion-template.yaml new file mode 100644 index 00000000000..716a48456d4 --- /dev/null +++ b/pkg/readiness/testdata/99-expansion-template.yaml @@ -0,0 +1,15 @@ +apiVersion: expansion.gatekeeper.sh/v1alpha1 +kind: ExpansionTemplate +metadata: + name: demo +spec: + applyTo: + - groups: [ "apps" ] + kinds: [ "Deployment", "ReplicaSet" ] + versions: [ "v1" ] + templateSource: "spec.template" + enforcementAction: "deny" + generatedGVK: + kind: "Pod" + group: "" + version: "v1" diff --git a/pkg/readiness/testdata_test.go b/pkg/readiness/testdata_test.go index c91145bc366..2c39835aa50 100644 --- a/pkg/readiness/testdata_test.go +++ b/pkg/readiness/testdata_test.go @@ -19,6 +19,8 @@ import ( externaldatav1beta1 "github.com/open-policy-agent/frameworks/constraint/pkg/apis/externaldata/v1beta1" "github.com/open-policy-agent/frameworks/constraint/pkg/core/templates" mutationsv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/mutations/v1alpha1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -63,6 +65,8 @@ var testProvider = []*externaldatav1beta1.Provider{ makeProvider("demo"), } +var testNS = makeNS("demo") + func makeTemplate(name string) *templates.ConstraintTemplate { return &templates.ConstraintTemplate{ ObjectMeta: metav1.ObjectMeta{ @@ -154,3 +158,39 @@ func makeProvider(name string) *externaldatav1beta1.Provider { }, } } + +func makeDeployment(name string) *appsv1.Deployment { + return &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: "apps/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "nginx", + Image: "nginx:latest", + }, + }, + }, + }, + }, + } +} + +func makeNS(name string) *corev1.Namespace { + return &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} diff --git a/pkg/webhook/policy.go b/pkg/webhook/policy.go index 0159f716348..6466caf4b71 100644 --- a/pkg/webhook/policy.go +++ b/pkg/webhook/policy.go @@ -34,6 +34,7 @@ import ( "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" rtypes "github.com/open-policy-agent/frameworks/constraint/pkg/types" "github.com/open-policy-agent/gatekeeper/apis" + expansionunversioned "github.com/open-policy-agent/gatekeeper/apis/expansion/unversioned" mutationsunversioned "github.com/open-policy-agent/gatekeeper/apis/mutations/unversioned" "github.com/open-policy-agent/gatekeeper/pkg/controller/config/process" "github.com/open-policy-agent/gatekeeper/pkg/expansion" @@ -322,6 +323,8 @@ func (h *validationHandler) validateGatekeeperResources(ctx context.Context, req switch { case gvk.Group == "templates.gatekeeper.sh" && gvk.Kind == "ConstraintTemplate": return h.validateTemplate(ctx, req) + case gvk.Group == "expansion.gatekeeper.sh" && gvk.Kind == "ExpansionTemplate": + return h.validateExpansionTemplate(req) case gvk.Group == "constraints.gatekeeper.sh": return h.validateConstraint(req) case gvk.Group == "config.gatekeeper.sh" && gvk.Kind == "Config": @@ -407,6 +410,23 @@ func (h *validationHandler) validateConstraint(req *admission.Request) (bool, er return false, nil } +func (h *validationHandler) validateExpansionTemplate(req *admission.Request) (bool, error) { + obj, _, err := deserializer.Decode(req.AdmissionRequest.Object.Raw, nil, nil) + if err != nil { + return false, err + } + unversioned := &expansionunversioned.ExpansionTemplate{} + if err := runtimeScheme.Convert(obj, unversioned, nil); err != nil { + return false, err + } + err = expansion.ValidateTemplate(unversioned) + if err != nil { + return true, err + } + + return false, nil +} + func (h *validationHandler) validateConfigResource(req *admission.Request) error { if req.Name != keys.Config.Name { return fmt.Errorf("config resource must have name 'config'") diff --git a/test/bats/test.bats b/test/bats/test.bats index 307540d715a..4a53e1f1186 100644 --- a/test/bats/test.bats +++ b/test/bats/test.bats @@ -417,6 +417,12 @@ __expansion_audit_test() { run kubectl apply -f test/expansion/loadbalancers_must_have_env.yaml wait_for_process ${WAIT_TIME} ${SLEEP_TIME} "constraint_enforced k8srequiredlabels loadbalancers-must-have-env" + # check status resource on expansion template + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} "kubectl get -f test/expansion/expand_deployments.yaml -ojson | jq -r -e '.status.byPod[0]'" + local temp_uid=$(kubectl get -f test/expansion/expand_deployments.yaml -o jsonpath='{.metadata.uid}') + local byPod_uid=$(kubectl get -f test/expansion/expand_deployments.yaml -o jsonpath='{.status.byPod[0].templateUID}') + assert_match ${temp_uid} ${byPod_uid} + # assert that creating deployment without 'env' label is rejected run kubectl apply -f test/expansion/deployment_no_label.yaml assert_failure diff --git a/test/testutils/controller.go b/test/testutils/controller.go new file mode 100644 index 00000000000..050dd1c8413 --- /dev/null +++ b/test/testutils/controller.go @@ -0,0 +1,179 @@ +package testutils + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "testing" + "time" + + "github.com/open-policy-agent/gatekeeper/apis" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +var ( + vendorCRDPath = []string{"vendor", "github.com", "open-policy-agent", "frameworks", "constraint", "deploy", "crds.yaml"} + gkCRDPath = []string{"config", "crd", "bases"} +) + +// ConstantRetry makes 3,000 attempts at a rate of 100 per second. Since this +// is a test instance and not a "real" cluster, this is fine and there's no need +// to increase the wait time each iteration. +var ConstantRetry = wait.Backoff{ + Steps: 3000, + Duration: 10 * time.Millisecond, +} + +// CreateGatekeeperNamespace bootstraps the gatekeeper-system namespace for use in tests. +func CreateGatekeeperNamespace(cfg *rest.Config) error { + c, err := client.New(cfg, client.Options{}) + if err != nil { + return err + } + + // Create gatekeeper namespace + ns := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gatekeeper-system", + }, + } + + ctx := context.Background() + _, err = controllerutil.CreateOrUpdate(ctx, c, ns, func() error { return nil }) + return err +} + +// DeleteObjectAndConfirm returns a callback which deletes obj from the passed +// Client. Does result in mutations to obj. The callback includes a cached copy +// of all information required to delete obj in the callback, so it is safe to +// mutate obj afterwards. Similarly - client.Delete mutates its input, but +// the callback does not call client.Delete on obj. Instead, it creates a +// single-purpose Unstructured for this purpose. Thus, obj is not mutated after +// the callback is run. +func DeleteObjectAndConfirm(ctx context.Context, t *testing.T, c client.Client, obj client.Object) func() { + t.Helper() + + // Cache the identifying information from obj. We refer to this cached + // information in the callback, and not obj itself. + gvk := obj.GetObjectKind().GroupVersionKind() + namespace := obj.GetNamespace() + name := obj.GetName() + + if gvk.Empty() { + // We can't send a proper delete request with an Unstructured without + // filling in GVK. The alternative would be to require tests to construct + // a valid Scheme or provide a factory method for the type to delete - this + // is easier. + t.Fatalf("gvk for %v/%v %T is empty", + namespace, name, obj) + } + + return func() { + t.Helper() + + // Construct a single-use Unstructured to send the Delete request. + toDelete := makeUnstructured(gvk, namespace, name) + err := c.Delete(ctx, toDelete) + if apierrors.IsNotFound(err) { + return + } else if err != nil { + t.Fatal(err) + } + + err = retry.OnError(ConstantRetry, func(err error) bool { + return true + }, func() error { + // Construct a single-use Unstructured to send the Get request. It isn't + // safe to reuse Unstructureds for each retry as Get modifies its input. + toGet := makeUnstructured(gvk, namespace, name) + key := client.ObjectKey{Namespace: namespace, Name: name} + err2 := c.Get(ctx, key, toGet) + if apierrors.IsGone(err2) || apierrors.IsNotFound(err2) { + return nil + } + + // Marshal the currently-gotten object, so it can be printed in test + // failure output. + s, _ := json.MarshalIndent(toGet, "", " ") + return fmt.Errorf("found %v %v:\n%s", gvk, key, string(s)) + }) + + if err != nil { + t.Fatal(err) + } + } +} + +func StartControlPlane(m *testing.M, cfg **rest.Config, testerDepth int) { + walkbacks := make([]string, testerDepth) + for i := 0; i < testerDepth; i++ { + walkbacks[i] = ".." + } + t := &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join(append(walkbacks, vendorCRDPath...)...), + filepath.Join(append(walkbacks, gkCRDPath...)...), + }, + ErrorIfCRDPathMissing: true, + } + if err := apis.AddToScheme(scheme.Scheme); err != nil { + log.Fatal(err) + } + + var err error + if *cfg, err = t.Start(); err != nil { + log.Fatal(err) + } + log.Print("STARTED") + + code := m.Run() + if err = t.Stop(); err != nil { + log.Printf("error while trying to stop server: %v", err) + } + os.Exit(code) +} + +// CreateThenCleanup creates obj in Client, and then registers obj to be deleted +// at the end of the test. The passed obj is safely deepcopied before being +// passed to client.Create, so it is not mutated by this call. +func CreateThenCleanup(ctx context.Context, t *testing.T, c client.Client, obj client.Object) { + t.Helper() + cpy := obj.DeepCopyObject() + cpyObj, ok := cpy.(client.Object) + if !ok { + t.Fatalf("got obj.DeepCopyObject() type = %T, want %T", cpy, client.Object(nil)) + } + + err := c.Create(ctx, cpyObj) + if err != nil { + t.Fatal(err) + } + + // It is unnecessary to deepcopy obj as deleteObjectAndConfirm does not pass + // obj to any Client calls. + t.Cleanup(DeleteObjectAndConfirm(ctx, t, c, obj)) +} + +func makeUnstructured(gvk schema.GroupVersionKind, namespace, name string) *unstructured.Unstructured { + u := &unstructured.Unstructured{ + Object: make(map[string]interface{}), + } + u.SetGroupVersionKind(gvk) + u.SetNamespace(namespace) + u.SetName(name) + return u +} diff --git a/test/testutils/manager.go b/test/testutils/manager.go index bc5ded5b27a..b3b255c9027 100644 --- a/test/testutils/manager.go +++ b/test/testutils/manager.go @@ -5,7 +5,14 @@ import ( "sync" "testing" + "github.com/open-policy-agent/gatekeeper/pkg/watch" + "github.com/open-policy-agent/gatekeeper/third_party/sigs.k8s.io/controller-runtime/pkg/dynamiccache" + "github.com/prometheus/client_golang/prometheus" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/metrics" ) // StartManager starts mgr. Registers a cleanup function to stop the manager at the completion of the test. @@ -30,3 +37,34 @@ func StartManager(ctx context.Context, t *testing.T, mgr manager.Manager) { } }) } + +// SetupManager sets up a controller-runtime manager with registered watch manager. +func SetupManager(t *testing.T, cfg *rest.Config) (manager.Manager, *watch.Manager) { + t.Helper() + + metrics.Registry = prometheus.NewRegistry() + mgr, err := manager.New(cfg, manager.Options{ + MetricsBindAddress: "0", + NewCache: dynamiccache.New, + MapperProvider: func(c *rest.Config) (meta.RESTMapper, error) { + return apiutil.NewDynamicRESTMapper(c) + }, + Logger: NewLogger(t), + }) + if err != nil { + t.Fatalf("setting up controller manager: %s", err) + } + c := mgr.GetCache() + dc, ok := c.(watch.RemovableCache) + if !ok { + t.Fatalf("expected dynamic cache, got: %T", c) + } + wm, err := watch.New(dc) + if err != nil { + t.Fatalf("could not create watch manager: %s", err) + } + if err := mgr.Add(wm); err != nil { + t.Fatalf("could not add watch manager to manager: %s", err) + } + return mgr, wm +} From dfb87b67b380ba098983b972b387645e8d5531b6 Mon Sep 17 00:00:00 2001 From: Anlan Du Date: Tue, 4 Apr 2023 19:55:21 -0700 Subject: [PATCH 6/7] docs: Add sync resource proposal to design docs (#2674) Signed-off-by: Anlan Du Co-authored-by: Max Smythe Signed-off-by: Matthias Teich --- docs/design/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/design/README.md b/docs/design/README.md index e523ab947c8..5733f76ef64 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -18,6 +18,7 @@ Generally, design docs are on Google docs: * [Mutation Initial Design Doc (April 2019)](https://docs.google.com/document/d/1qTHwqoUX8AL2jodyWKB_2szrGDwhi14Ra_LlQ-ogtck/edit#heading=h.iu1ppjy7g7j) * [External Data](https://docs.google.com/document/d/1hPi86jdsCKg8puYT5_s_73mPGExUJeZfyKmvG-XWtPc/edit#) * [gator validate](https://docs.google.com/document/d/1B0hXDia8SExOkCVAEbPVHFJmnWeJgHziGvMxnzrENa0/edit) +* [Sync Resource](https://docs.google.com/document/d/1ZbXaEh7v_HcgrRu7N-kEucU2pXyq1Rt7cSmmFOBN2q8/edit) ## Implemented * [V3 Accepted Design](https://docs.google.com/document/d/1yC4wgpVoJj6ngYnSTtO-HeaIBl05gla562sD7qKPy3M/edit#heading=h.z0bjqzl81dpe) From d70347e5f5b14b4e01f44dea213843bb20df5c41 Mon Sep 17 00:00:00 2001 From: Matthias Teich Date: Wed, 5 Apr 2023 15:02:11 +0200 Subject: [PATCH 7/7] docs: fix typos Signed-off-by: Matthias Teich --- website/docs/expansion.md | 2 +- website/versioned_docs/version-v3.10.x/expansion.md | 2 +- website/versioned_docs/version-v3.10.x/workload-resources.md | 2 +- website/versioned_docs/version-v3.11.x/expansion.md | 2 +- website/versioned_docs/version-v3.11.x/workload-resources.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/docs/expansion.md b/website/docs/expansion.md index adf1d5cf75d..6b46ebab428 100644 --- a/website/docs/expansion.md +++ b/website/docs/expansion.md @@ -35,7 +35,7 @@ webhooks, Gatekeeper leverages its feature to allow mock resources to be manipulated into their desired form. In the Istio example, `Assign` and `ModifySet` mutators could be configured to mimic Istio sidecar injection. For further details on mutating mock resources -see the [Math Source](#match-source) section below, or to see a working example, +see the [Match Source](#match-source) section below, or to see a working example, see the [Example](#example) section. Any resources configured for expansion will be expanded by both the validating diff --git a/website/versioned_docs/version-v3.10.x/expansion.md b/website/versioned_docs/version-v3.10.x/expansion.md index ce561c9aa01..85c7d49ddcb 100644 --- a/website/versioned_docs/version-v3.10.x/expansion.md +++ b/website/versioned_docs/version-v3.10.x/expansion.md @@ -35,7 +35,7 @@ webhooks, Gatekeeper leverages its feature to allow mock resources to be manipulated into their desired form. In the Istio example, `Assign` and `ModifySet` mutators could be configured to mimic Istio sidecar injection. For further details on mutating mock resources -see the [Math Source](#match-source) section below, or to see a working example, +see the [Match Source](#match-source) section below, or to see a working example, see the [Example](#example) section. Any resources configured for expansion will be expanded by both the validating diff --git a/website/versioned_docs/version-v3.10.x/workload-resources.md b/website/versioned_docs/version-v3.10.x/workload-resources.md index 2edc0284a2a..527b10da71f 100644 --- a/website/versioned_docs/version-v3.10.x/workload-resources.md +++ b/website/versioned_docs/version-v3.10.x/workload-resources.md @@ -7,7 +7,7 @@ title: Working with Workload Resources [Workload resources](https://kubernetes.io/docs/concepts/workloads/) are Kubernetes resources like Deployments or DaemonSets that create Pods by-way-of a controller. Because many Gatekeeper validation policies are written to enforce against Pods, like those found in the [Gatekeeper policy library](https://www.github.com/open-policy-agent/gatekeeper-library), it is important to recognize that Gatekeeper Pod violation messages will not be directly reported to the user when using the library as those Pods are created from workload resources. -To reject workload resources that can create a resource that violates a constraint, checkout the [Validation of Workflow Resources](expansion.md) feature available in Gatekeeper v3.10+. +To reject workload resources that can create a resource that violates a constraint, checkout the [Validation of Workload Resources](expansion.md) feature available in Gatekeeper v3.10+. ### Example diff --git a/website/versioned_docs/version-v3.11.x/expansion.md b/website/versioned_docs/version-v3.11.x/expansion.md index 0e9322c3f64..ddde4bf38b8 100644 --- a/website/versioned_docs/version-v3.11.x/expansion.md +++ b/website/versioned_docs/version-v3.11.x/expansion.md @@ -35,7 +35,7 @@ webhooks, Gatekeeper leverages its feature to allow mock resources to be manipulated into their desired form. In the Istio example, `Assign` and `ModifySet` mutators could be configured to mimic Istio sidecar injection. For further details on mutating mock resources -see the [Math Source](#match-source) section below, or to see a working example, +see the [Match Source](#match-source) section below, or to see a working example, see the [Example](#example) section. Any resources configured for expansion will be expanded by both the validating diff --git a/website/versioned_docs/version-v3.11.x/workload-resources.md b/website/versioned_docs/version-v3.11.x/workload-resources.md index 2edc0284a2a..527b10da71f 100644 --- a/website/versioned_docs/version-v3.11.x/workload-resources.md +++ b/website/versioned_docs/version-v3.11.x/workload-resources.md @@ -7,7 +7,7 @@ title: Working with Workload Resources [Workload resources](https://kubernetes.io/docs/concepts/workloads/) are Kubernetes resources like Deployments or DaemonSets that create Pods by-way-of a controller. Because many Gatekeeper validation policies are written to enforce against Pods, like those found in the [Gatekeeper policy library](https://www.github.com/open-policy-agent/gatekeeper-library), it is important to recognize that Gatekeeper Pod violation messages will not be directly reported to the user when using the library as those Pods are created from workload resources. -To reject workload resources that can create a resource that violates a constraint, checkout the [Validation of Workflow Resources](expansion.md) feature available in Gatekeeper v3.10+. +To reject workload resources that can create a resource that violates a constraint, checkout the [Validation of Workload Resources](expansion.md) feature available in Gatekeeper v3.10+. ### Example