diff --git a/docs/reference/domains/kubernetes-domain.md b/docs/reference/domains/kubernetes-domain.md index 4a50b140..6d1026cf 100644 --- a/docs/reference/domains/kubernetes-domain.md +++ b/docs/reference/domains/kubernetes-domain.md @@ -29,18 +29,19 @@ domain: base64: # Optional - Boolean whether field is base64 encoded ``` -> [!Tip] -> Lula supports eventual-consistency through use of an optional `wait` field in the `kubernetes-spec`. +Lula supports eventual-consistency through use of an optional `wait` field in the `kubernetes-spec`. This parameter supports waiting for a specified resource to be `Ready` in the cluster. This may be particularly useful if evaluating the status of a selected resource or evaluating the children of a specified resource. ```yaml domain: type: kubernetes kubernetes-spec: wait: # Optional - Group of resources to read from Kubernetes - condition: Ready # ... - kind: pod/test-pod-wait # ... - namespace: validation-test # ... - timeout: 30s # ... + group: # Optional - Empty or "" for core group + version: v1 # Required - Version of resource + resource: pods # Required - Resource type (API-recognized type, not Kind)Required - Resource type (API-recognized type, not Kind) + name: test-pod-wait # Required - Name of the resource to wait for + namespace: validation-test # Optional - For namespaced resources + timeout: 30s # Optional - Defaults to 30s resources: - name: podsvt resource-rule: @@ -50,6 +51,9 @@ domain: namespaces: [validation-test] ``` +> [!Tip] +> Both `resources` and `wait` use the Group, Version, Resource constructs to identify the resource to be evaluated. To identify those using `kubectl`, executing `kubectl explain ` will provide the Group and Version, the `resource` field is the API-recognized type and can be confirmed by consulting the list provided by `kubectl api-resources`. + ### Resource Creation The Kubernetes domain also supports creating, reading, and destroying test resources in the cluster. This feature should be used with caution since it's writing to the cluster and ideally should be implemented on separate namespaces to make any erroneous outcomes easier to mitigate. diff --git a/go.mod b/go.mod index 24348440..e5f3001b 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/charmbracelet/x/exp/teatest v0.0.0-20240919170804-a4978c8e603a github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/defenseunicorns/go-oscal v0.6.0 + github.com/defenseunicorns/pkg/kubernetes v0.3.0 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-version v1.7.0 github.com/kyverno/kyverno-json v0.0.3 @@ -24,9 +25,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.1 k8s.io/apimachinery v0.31.1 - k8s.io/cli-runtime v0.31.1 k8s.io/client-go v0.31.1 - k8s.io/kubectl v0.31.1 + sigs.k8s.io/cli-utils v0.36.0 sigs.k8s.io/e2e-framework v0.4.0 sigs.k8s.io/kustomize/kyaml v0.17.2 sigs.k8s.io/yaml v1.4.0 @@ -42,7 +42,6 @@ require ( github.com/CycloneDX/cyclonedx-go v0.9.0 // indirect github.com/IGLOU-EU/go-wildcard v1.0.3 // indirect github.com/KeisukeYamashita/go-vcl v0.4.0 // indirect - github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/agnivade/levenshtein v1.2.0 // indirect @@ -57,7 +56,6 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/bufbuild/protocompile v0.6.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chai2010/gettext-go v1.0.2 // indirect github.com/charmbracelet/x/ansi v0.3.2 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20240919170804-a4978c8e603a // indirect github.com/charmbracelet/x/term v0.2.0 // indirect @@ -65,14 +63,10 @@ require ( github.com/containerd/console v1.0.4 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/daviddengcn/go-colortext v1.0.0 // indirect - github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/camelcase v1.0.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-akka/configuration v0.0.0-20200606091224-a002c0330665 // indirect @@ -86,7 +80,6 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-jsonnet v0.20.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -103,14 +96,11 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172 // indirect - github.com/jonboulle/clockwork v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/kyverno/pkg/ext v0.0.0-20240418121121-df8add26c55c // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/lithammer/dedent v1.1.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -122,7 +112,6 @@ require ( github.com/moby/buildkit v0.15.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/spdystream v0.4.0 // indirect - github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect @@ -133,7 +122,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.20.4 // indirect @@ -187,17 +175,16 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/cli-runtime v0.31.1 // indirect k8s.io/component-base v0.31.1 // indirect - k8s.io/component-helpers v0.31.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/metrics v0.31.1 // indirect + k8s.io/kubectl v0.31.1 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect muzzammil.xyz/jsonc v1.0.0 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect sigs.k8s.io/controller-runtime v0.18.2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.17.2 // indirect - sigs.k8s.io/kustomize/kustomize/v5 v5.4.2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index d118a088..4f397efb 100644 --- a/go.sum +++ b/go.sum @@ -111,24 +111,20 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE= -github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/defenseunicorns/go-oscal v0.6.0 h1:eflEKfk7edu4L4kWf6aNQpS94ljfGP8lgWpsPYNtE1Q= github.com/defenseunicorns/go-oscal v0.6.0/go.mod h1:UHp2yK9ty2mYJDun7oNhbstCq6SAAwP4YGbw9n7uG6o= +github.com/defenseunicorns/pkg/kubernetes v0.3.0 h1:f4VSIaUdvn87/dhiZvRbUfHhcHa8bKia6aU0WcvPbYg= +github.com/defenseunicorns/pkg/kubernetes v0.3.0/go.mod h1:FsuKQGpPZOnZWifBse7v787+avtIu2lte5LTsaojDkY= github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -149,8 +145,6 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -207,11 +201,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= -github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A= -github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE= -github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ= -github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v22.9.29+incompatible h1:3UBb679lq3V/O9rgzoJmnkP1jJzmC9OdFzITUBkLU/A= @@ -266,8 +255,6 @@ github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172 h1:XQYEhx+bEiWn6eiHFivu4wEHm91FoZ/gCvoLZK6Ze5Y= github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172/go.mod h1:j4OeykGPBbhX3rw4AOPGXSmX2/zuWXktm704A4MtHFs= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -302,8 +289,6 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -671,22 +656,20 @@ k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= -k8s.io/component-helpers v0.31.1 h1:5hZUf3747atdgtR3gPntrG35rC2CkK7rYq2KUraz6Os= -k8s.io/component-helpers v0.31.1/go.mod h1:ye0Gi8KzFNTfpIuzvVDtxJQMP/0Owkukf1vGf22Hl6U= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubectl v0.31.1 h1:ih4JQJHxsEggFqDJEHSOdJ69ZxZftgeZvYo7M/cpp24= k8s.io/kubectl v0.31.1/go.mod h1:aNuQoR43W6MLAtXQ/Bu4GDmoHlbhHKuyD49lmTC8eJM= -k8s.io/metrics v0.31.1 h1:h4I4dakgh/zKflWYAOQhwf0EXaqy8LxAIyE/GBvxqRc= -k8s.io/metrics v0.31.1/go.mod h1:JuH1S9tJiH9q1VCY0yzSCawi7kzNLsDzlWDJN4xR+iA= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= muzzammil.xyz/jsonc v1.0.0 h1:B6kaT3wHueZ87mPz3q1nFuM1BlL32IG0wcq0/uOsQ18= muzzammil.xyz/jsonc v1.0.0/go.mod h1:rFv8tUUKe+QLh7v02BhfxXEf4ZHhYD7unR93HL/1Uvo= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= +sigs.k8s.io/cli-utils v0.36.0 h1:k7GM6LmIMydtvM6Ad91XuqKk0QEVL9bVbaiX1uvWIrA= +sigs.k8s.io/cli-utils v0.36.0/go.mod h1:uCFC3BPXB3xHFQyKkWUlTrncVDCKzbdDfqZqRTCrk24= sigs.k8s.io/controller-runtime v0.18.2 h1:RqVW6Kpeaji67CY5nPEfRz6ZfFMk0lWQlNrLqlNpx+Q= sigs.k8s.io/controller-runtime v0.18.2/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/e2e-framework v0.4.0 h1:4yYmFDNNoTnazqmZJXQ6dlQF1vrnDbutmxlyvBpC5rY= @@ -695,8 +678,6 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= -sigs.k8s.io/kustomize/kustomize/v5 v5.4.2 h1:9Zl5Gqg3XMdBEvkR54pVLCBj7FVO7W+VPNDDEzD6AyE= -sigs.k8s.io/kustomize/kustomize/v5 v5.4.2/go.mod h1:5ypfJVYlPb2MKKeoGknVLxvHemDlQT+szI4+KOhnD6k= sigs.k8s.io/kustomize/kyaml v0.17.2 h1:+AzvoJUY0kq4QAhH/ydPHHMRLijtUKiyVyh7fOSshr0= sigs.k8s.io/kustomize/kyaml v0.17.2/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/src/cmd/dev/common.go b/src/cmd/dev/common.go index d8e065b6..bd177d33 100644 --- a/src/cmd/dev/common.go +++ b/src/cmd/dev/common.go @@ -83,7 +83,7 @@ func ReadValidation(cmd *cobra.Command, spinner *message.Spinner, path string, t } // RunSingleValidation runs a single validation -func RunSingleValidation(validationBytes []byte, opts ...types.LulaValidationOption) (lulaValidation types.LulaValidation, err error) { +func RunSingleValidation(ctx context.Context, validationBytes []byte, opts ...types.LulaValidationOption) (lulaValidation types.LulaValidation, err error) { var validation common.Validation err = yaml.Unmarshal(validationBytes, &validation) @@ -96,7 +96,7 @@ func RunSingleValidation(validationBytes []byte, opts ...types.LulaValidationOpt return lulaValidation, err } - err = lulaValidation.Validate(context.Background(), opts...) + err = lulaValidation.Validate(ctx, opts...) if err != nil { return lulaValidation, err } diff --git a/src/cmd/dev/get-resources.go b/src/cmd/dev/get-resources.go index a3e2ac4a..0a550957 100644 --- a/src/cmd/dev/get-resources.go +++ b/src/cmd/dev/get-resources.go @@ -72,7 +72,7 @@ func init() { } func DevGetResources(ctx context.Context, validationBytes []byte, spinner *message.Spinner) (types.DomainResources, error) { - lulaValidation, err := RunSingleValidation( + lulaValidation, err := RunSingleValidation(ctx, validationBytes, types.ExecutionAllowed(getResourcesOpts.ConfirmExecution), types.Interactive(RunInteractively), diff --git a/src/cmd/dev/validate.go b/src/cmd/dev/validate.go index b99c40fc..4bd3d256 100644 --- a/src/cmd/dev/validate.go +++ b/src/cmd/dev/validate.go @@ -132,7 +132,7 @@ func DevValidate(ctx context.Context, validationBytes []byte, resourcesBytes []b } } - lulaValidation, err = RunSingleValidation( + lulaValidation, err = RunSingleValidation(ctx, validationBytes, types.WithStaticResources(resources), types.ExecutionAllowed(validateOpts.ConfirmExecution), diff --git a/src/pkg/common/common.go b/src/pkg/common/common.go index 66f45df2..01decad5 100644 --- a/src/pkg/common/common.go +++ b/src/pkg/common/common.go @@ -140,13 +140,13 @@ func SetCwdToFileDir(dirPath string) (resetFunc func(), err error) { } // Get the domain and providers -func GetDomain(domain *Domain, ctx context.Context) (types.Domain, error) { +func GetDomain(domain *Domain) (types.Domain, error) { if domain == nil { return nil, fmt.Errorf("domain is nil") } switch domain.Type { case "kubernetes": - return kube.CreateKubernetesDomain(ctx, domain.KubernetesSpec) + return kube.CreateKubernetesDomain(domain.KubernetesSpec) case "api": return api.CreateApiDomain(domain.ApiSpec) case "file": diff --git a/src/pkg/common/common_test.go b/src/pkg/common/common_test.go index 8243bf9e..267cd5c5 100644 --- a/src/pkg/common/common_test.go +++ b/src/pkg/common/common_test.go @@ -116,11 +116,9 @@ func TestGetDomain(t *testing.T) { }, } - ctx := context.Background() - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := common.GetDomain(&tt.domain, ctx) + result, err := common.GetDomain(&tt.domain) if (err != nil) != tt.expectedErr { t.Fatalf("expected error: %v, got: %v", tt.expectedErr, err) } diff --git a/src/pkg/common/schemas/validation.json b/src/pkg/common/schemas/validation.json index 0d733ed4..c452ab43 100644 --- a/src/pkg/common/schemas/validation.json +++ b/src/pkg/common/schemas/validation.json @@ -181,17 +181,21 @@ "wait": { "type": "object", "properties": { - "condition": { + "name": { "type": "string", - "description": "Condition to wait for ie. 'Ready'" + "description": "Name of the resource to wait for" }, - "jsonpath": { + "group": { "type": "string", - "description": "Jsonpath specifier of where to find the condition from the top level object" + "description": "Empty or \"\" for core group" }, - "kind": { + "version": { "type": "string", - "description": "Kind of resource to wait for" + "description": "Version of resource" + }, + "resource": { + "type": "string", + "description": "Resource type (API-recognized type, not Kind)" }, "namespace": { "type": "string", @@ -201,7 +205,12 @@ "type": "string", "description": "Timeout for the wait" } - } + }, + "required": [ + "name", + "version", + "resource" + ] } }, "anyOf": [ diff --git a/src/pkg/common/types.go b/src/pkg/common/types.go index 3260ba9d..e401eb42 100644 --- a/src/pkg/common/types.go +++ b/src/pkg/common/types.go @@ -150,7 +150,7 @@ func (validation *Validation) ToLulaValidation(uuid string) (lulaValidation type // TODO: Is there a better location for context? ctx := context.Background() - domain, err := GetDomain(validation.Domain, ctx) + domain, err := GetDomain(validation.Domain) if domain == nil { return lulaValidation, fmt.Errorf("%w: %s", ErrInvalidDomain, validation.Domain.Type) } else if err != nil { diff --git a/src/pkg/domains/kubernetes/cluster.go b/src/pkg/domains/kubernetes/cluster.go new file mode 100644 index 00000000..bf4fc03f --- /dev/null +++ b/src/pkg/domains/kubernetes/cluster.go @@ -0,0 +1,93 @@ +package kube + +import ( + "errors" + "fmt" + "sync" + + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/cli-utils/pkg/kstatus/watcher" + "sigs.k8s.io/e2e-framework/klient" +) + +var ( + clusterConnectOnce sync.Once + globalCluster *Cluster + globalConnectionErr error +) + +type Cluster struct { + clientset kubernetes.Interface + kclient klient.Client + watcher watcher.StatusWatcher + dynamicClient *dynamic.DynamicClient +} + +func GetCluster() (*Cluster, error) { + clusterConnectOnce.Do(func() { + globalCluster, globalConnectionErr = New() + }) + + return globalCluster, globalConnectionErr +} + +func New() (*Cluster, error) { + clusterErr := errors.New("unable to connect to the cluster") + clientset, config, err := pkgkubernetes.ClientAndConfig() + if err != nil { + return nil, errors.Join(clusterErr, err) + } + + watcher, err := pkgkubernetes.WatcherForConfig(config) + if err != nil { + return nil, errors.Join(clusterErr, err) + } + + kclient, err := klient.New(config) + if err != nil { + return nil, errors.Join(clusterErr, err) + } + + dynamicClient := dynamic.NewForConfigOrDie(config) + + // Ensure no errors were returned to validate cluster connection. + _, err = clientset.Discovery().ServerVersion() + if err != nil { + return nil, errors.Join(clusterErr, err) + } + + return &Cluster{ + clientset: clientset, + kclient: kclient, + watcher: watcher, + dynamicClient: dynamicClient, + }, nil +} + +func (c *Cluster) validateAndGetGVR(group, version, resource string) (*metav1.APIResource, error) { + // Create a discovery client + discoveryClient := c.clientset.Discovery() + + // Get a list of all API resources for the given group version + gv := schema.GroupVersion{ + Group: group, + Version: version, + } + resourceList, err := discoveryClient.ServerResourcesForGroupVersion(gv.String()) + if err != nil { + return nil, err + } + + // Search for the specified resource in the list + for _, apiResource := range resourceList.APIResources { + if apiResource.Name == resource { + return &apiResource, nil + } + } + + return nil, fmt.Errorf("resource %s not found in group %s version %s", resource, group, version) +} diff --git a/src/pkg/domains/kubernetes/create.go b/src/pkg/domains/kubernetes/create.go index 42857b63..53add621 100644 --- a/src/pkg/domains/kubernetes/create.go +++ b/src/pkg/domains/kubernetes/create.go @@ -23,19 +23,13 @@ import ( ) // CreateE2E() creates the test resources, reads status, and destroys them -func CreateE2E(ctx context.Context, resources []CreateResource) (map[string]interface{}, error) { +func CreateE2E(ctx context.Context, cluster *Cluster, resources []CreateResource) (map[string]interface{}, error) { collections := make(map[string]interface{}, len(resources)) namespaces := make([]string, 0) var errList []string - // Set up the clients - config, err := connect() - if err != nil { - return nil, fmt.Errorf("failed to connect to k8s cluster: %w", err) - } - client, err := klient.New(config) - if err != nil { - return nil, fmt.Errorf("failed to create e2e client: %w", err) + if cluster == nil { + return nil, fmt.Errorf("cluster is nil") } // Create the resources, collect the outcome @@ -44,7 +38,7 @@ func CreateE2E(ctx context.Context, resources []CreateResource) (map[string]inte var err error // Create namespace if specified if resource.Namespace != "" { - new, err := createNamespace(ctx, client, resource.Namespace) + new, err := createNamespace(ctx, cluster.kclient, resource.Namespace) if err != nil { message.Debugf("error creating namespace %s: %v", resource.Namespace, err) errList = append(errList, err.Error()) @@ -58,13 +52,13 @@ func CreateE2E(ctx context.Context, resources []CreateResource) (map[string]inte // TODO: Allow both Manifest and File to be specified? // Want to catch any errors and proceed in case resources have already been created if resource.Manifest != "" { - collection, err = CreateFromManifest(ctx, client, []byte(resource.Manifest)) + collection, err = CreateFromManifest(ctx, cluster.kclient, []byte(resource.Manifest)) if err != nil { message.Debugf("error creating resource from manifest: %v", err) errList = append(errList, err.Error()) } } else if resource.File != "" { - collection, err = CreateFromFile(ctx, client, resource.File) + collection, err = CreateFromFile(ctx, cluster.kclient, resource.File) if err != nil { message.Debugf("error creating resource from file: %v", err) errList = append(errList, err.Error()) @@ -77,7 +71,7 @@ func CreateE2E(ctx context.Context, resources []CreateResource) (map[string]inte } // Destroy the resources - if err = DestroyAllResources(ctx, client, collections, namespaces); err != nil { + if err := DestroyAllResources(ctx, cluster.kclient, collections, namespaces); err != nil { // If a resource can't be destroyed, return the error (include retry logic??) message.Debugf("error destroying all resources: %v", err) errList = append(errList, err.Error()) diff --git a/src/pkg/domains/kubernetes/resource.go b/src/pkg/domains/kubernetes/resource.go index 248b574d..57c99025 100644 --- a/src/pkg/domains/kubernetes/resource.go +++ b/src/pkg/domains/kubernetes/resource.go @@ -11,22 +11,21 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" ) // QueryCluster() requires context and a Payload as input and returns []unstructured.Unstructured // This function is used to query the cluster for all resources required for processing -func QueryCluster(ctx context.Context, resources []Resource) (map[string]interface{}, error) { +func QueryCluster(ctx context.Context, cluster *Cluster, resources []Resource) (map[string]interface{}, error) { + if cluster == nil { + return nil, fmt.Errorf("cluster is nil") + } // We may need a new type here to hold groups of resources collections := make(map[string]interface{}, 0) for _, resource := range resources { - collection, err := GetResourcesDynamically(ctx, resource.ResourceRule) + collection, err := GetResourcesDynamically(ctx, cluster, resource.ResourceRule) // log error but continue with other resources if err != nil { return nil, err @@ -47,17 +46,10 @@ func QueryCluster(ctx context.Context, resources []Resource) (map[string]interfa // GetResourcesDynamically() requires a dynamic interface and processes GVR to return []map[string]interface{} // This function is used to query the cluster for specific subset of resources required for processing -func GetResourcesDynamically(ctx context.Context, - resource *ResourceRule) ( - []map[string]interface{}, error) { +func GetResourcesDynamically(ctx context.Context, cluster *Cluster, resource *ResourceRule) ([]map[string]interface{}, error) { if resource == nil { return nil, fmt.Errorf("resource rule is nil") } - config, err := connect() - if err != nil { - return nil, fmt.Errorf("failed to connect to k8s cluster: %w", err) - } - dynamic := dynamic.NewForConfigOrDie(config) resourceId := schema.GroupVersionResource{ Group: resource.Group, @@ -77,7 +69,7 @@ func GetResourcesDynamically(ctx context.Context, } else if resource.Name != "" { // Extracting named resources can only occur here var itemObj *unstructured.Unstructured - itemObj, err := dynamic.Resource(resourceId).Namespace(namespaces[0]).Get(ctx, resource.Name, metav1.GetOptions{}) + itemObj, err := cluster.dynamicClient.Resource(resourceId).Namespace(namespaces[0]).Get(ctx, resource.Name, metav1.GetOptions{}) if err != nil { return nil, err } @@ -94,7 +86,7 @@ func GetResourcesDynamically(ctx context.Context, collection = append(collection, item) } else { for _, namespace := range namespaces { - list, err := dynamic.Resource(resourceId).Namespace(namespace). + list, err := cluster.dynamicClient.Resource(resourceId).Namespace(namespace). List(ctx, metav1.ListOptions{}) if err != nil { return nil, err @@ -111,39 +103,6 @@ func GetResourcesDynamically(ctx context.Context, return collection, nil } -func getGroupVersionResource(kind string) (gvr *schema.GroupVersionResource, err error) { - config, err := connect() - if err != nil { - return nil, fmt.Errorf("failed to connect to k8s cluster: %w", err) - } - name := strings.Split(kind, "/")[0] - - discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) - if err != nil { - return nil, err - } - - _, resourceList, _, err := discoveryClient.GroupsAndMaybeResources() - if err != nil { - - return nil, err - } - - for gv, list := range resourceList { - for _, item := range list.APIResources { - if item.SingularName == name { - return &schema.GroupVersionResource{ - Group: gv.Group, - Version: gv.Version, - Resource: item.Name, - }, nil - } - } - } - - return nil, fmt.Errorf("kind %s not found", kind) -} - // getFieldValue() looks up the field from a resource and returns a map[string]interface{} representation of the data func getFieldValue(item map[string]interface{}, field *Field) (map[string]interface{}, error) { if field == nil { @@ -219,19 +178,3 @@ func cleanResources(resources *[]map[string]interface{}) { } } } - -// Use the K8s "client-go" library to get the currently active kube context, in the same way that -// "kubectl" gets it if no extra config flags like "--kubeconfig" are passed. -func connect() (config *rest.Config, err error) { - // Build the config from the currently active kube context in the default way that the k8s client-go gets it, which - // is to look at the KUBECONFIG env var - config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - clientcmd.NewDefaultClientConfigLoadingRules(), - &clientcmd.ConfigOverrides{}).ClientConfig() - - if err != nil { - return nil, err - } - - return config, nil -} diff --git a/src/pkg/domains/kubernetes/types.go b/src/pkg/domains/kubernetes/types.go index 9da54acd..90351d0f 100644 --- a/src/pkg/domains/kubernetes/types.go +++ b/src/pkg/domains/kubernetes/types.go @@ -9,14 +9,11 @@ import ( ) type KubernetesDomain struct { - // Context is the context that Kubernetes resources are being evaluated in - Context context.Context `json:"context" yaml:"context"` - // Spec is the specification of the Kubernetes resources Spec *KubernetesSpec `json:"spec,omitempty" yaml:"spec,omitempty"` } -func CreateKubernetesDomain(ctx context.Context, spec *KubernetesSpec) (types.Domain, error) { +func CreateKubernetesDomain(spec *KubernetesSpec) (types.Domain, error) { // Check validity of spec if spec == nil { return nil, fmt.Errorf("spec is nil") @@ -59,11 +56,14 @@ func CreateKubernetesDomain(ctx context.Context, spec *KubernetesSpec) (types.Do } if spec.Wait != nil { - if spec.Wait.Kind == "" { - return nil, fmt.Errorf("wait kind cannot be empty") + if spec.Wait.Resource == "" { + return nil, fmt.Errorf("wait resource cannot be empty") + } + if spec.Wait.Version == "" { + return nil, fmt.Errorf("wait version cannot be empty") } - if spec.Wait.Condition != "" && spec.Wait.Jsonpath != "" { - return nil, fmt.Errorf("only one of wait.condition or wait.jsonpath can be specified") + if spec.Wait.Name == "" { + return nil, fmt.Errorf("wait name cannot be empty") } } @@ -82,15 +82,21 @@ func CreateKubernetesDomain(ctx context.Context, spec *KubernetesSpec) (types.Do } return KubernetesDomain{ - Context: ctx, - Spec: spec, + Spec: spec, }, nil } -func (k KubernetesDomain) GetResources(_ context.Context) (resources types.DomainResources, err error) { +// GetResources returns the resources from the Kubernetes domain +// Evaluates the `create-resources` first, `wait` second, and finally `resources` last +func (k KubernetesDomain) GetResources(ctx context.Context) (resources types.DomainResources, err error) { + cluster, err := GetCluster() + if err != nil { + return nil, err + } + // Evaluate the wait condition if k.Spec.Wait != nil { - err := EvaluateWait(*k.Spec.Wait) + err := EvaluateWait(ctx, cluster, *k.Spec.Wait) if err != nil { return nil, err } @@ -98,12 +104,12 @@ func (k KubernetesDomain) GetResources(_ context.Context) (resources types.Domai // TODO: Return both? if k.Spec.Resources != nil { - resources, err = QueryCluster(k.Context, k.Spec.Resources) + resources, err = QueryCluster(ctx, cluster, k.Spec.Resources) if err != nil { return nil, err } } else if k.Spec.CreateResources != nil { - resources, err = CreateE2E(k.Context, k.Spec.CreateResources) + resources, err = CreateE2E(ctx, cluster, k.Spec.CreateResources) if err != nil { return nil, err } @@ -163,9 +169,10 @@ func (f Field) Validate() error { } type Wait struct { - Condition string `json:"condition" yaml:"condition"` - Jsonpath string `json:"jsonpath" yaml:"jsonpath"` - Kind string `json:"kind" yaml:"kind"` + Name string `json:"name" yaml:"name"` + Group string `json:"group" yaml:"group"` + Version string `json:"version" yaml:"version"` + Resource string `json:"resource" yaml:"resource"` Namespace string `json:"namespace" yaml:"namespace"` Timeout string `json:"timeout" yaml:"timeout"` } diff --git a/src/pkg/domains/kubernetes/types_test.go b/src/pkg/domains/kubernetes/types_test.go index 6ea81cbf..8270cb55 100644 --- a/src/pkg/domains/kubernetes/types_test.go +++ b/src/pkg/domains/kubernetes/types_test.go @@ -1,7 +1,6 @@ package kube_test import ( - "context" "testing" kube "github.com/defenseunicorns/lula/src/pkg/domains/kubernetes" @@ -235,19 +234,19 @@ metadata: name: "valid wait", spec: &kube.KubernetesSpec{ Wait: &kube.Wait{ - Condition: "Ready", - Kind: "namespace/test", + Resource: "pods", + Version: "v1", + Name: "test", }, }, expectedErr: false, }, { - name: "invalid wait, both condition and jsonpath specified", + name: "invalid wait, no Resource or Name specified", spec: &kube.KubernetesSpec{ Wait: &kube.Wait{ - Condition: "Ready", - Jsonpath: "test", - Kind: "namespace/test", + Version: "v1", + Namespace: "test", }, }, expectedErr: true, @@ -256,7 +255,7 @@ metadata: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := kube.CreateKubernetesDomain(context.Background(), tt.spec) + _, err := kube.CreateKubernetesDomain(tt.spec) if (err != nil) != tt.expectedErr { t.Errorf("CreateKubernetesDomain() error = %v, wantErr %v", err, tt.expectedErr) } diff --git a/src/pkg/domains/kubernetes/wait.go b/src/pkg/domains/kubernetes/wait.go index 92d19efd..987ed30b 100644 --- a/src/pkg/domains/kubernetes/wait.go +++ b/src/pkg/domains/kubernetes/wait.go @@ -3,137 +3,45 @@ package kube import ( "context" "fmt" - "os" - "strings" "time" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/genericiooptions" - "k8s.io/kubectl/pkg/cmd" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/cmd/wait" + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/object" ) -// This is specific to Lula - Check if we need to execute any wait operations. -func EvaluateWait(waitPayload Wait) error { - var forCondition string - waitCmd := false - if waitPayload.Condition != "" { - forCondition = fmt.Sprintf("condition=%s", waitPayload.Condition) - waitCmd = true +func EvaluateWait(ctx context.Context, cluster *Cluster, waitPayload Wait) error { + if cluster == nil { + return fmt.Errorf("cluster is nil") } - if waitPayload.Jsonpath != "" { - if waitCmd { - return fmt.Errorf("only one of waitFor.condition or waitFor.jsonpath can be specified") - } - forCondition = fmt.Sprintf("jsonpath=%s", waitPayload.Jsonpath) - waitCmd = true + // TODO: incorporate wait for multiple objects? + obj, err := globalCluster.validateAndGetGVR(waitPayload.Group, waitPayload.Version, waitPayload.Resource) + if err != nil { + return fmt.Errorf("unable to validate GVR: %v", err) } - - if waitCmd { - var timeoutString string - if waitPayload.Timeout != "" { - timeoutString = fmt.Sprintf("%s", waitPayload.Timeout) - } else { - timeoutString = "5m" - } - - // Timeout control parameters - duration, err := time.ParseDuration(timeoutString) - expiration := time.Now().Add(duration) - startTime := time.Now() - - // Wait for existence - err = WaitForExistence(waitPayload.Kind, waitPayload.Namespace, duration) - if err != nil { - return err - } - - // If just waiting for existence - return here - switch waitPayload.Condition { - case "", "exist", "exists", "Exist", "Exists": - return nil - } - - // Calculate time remaining to explicitly pass as a timeout - timeoutRemaining := expiration.Sub(startTime) - - err = WaitForCondition(forCondition, waitPayload.Namespace, timeoutRemaining.String(), waitPayload.Kind) - if err != nil { - return err - } + objMeta := object.ObjMetadata{ + Name: waitPayload.Name, + Namespace: waitPayload.Namespace, + GroupKind: schema.GroupKind{ + Group: obj.Group, + Kind: obj.Kind, + }, } - return nil -} - -func WaitForExistence(kind string, namespace string, timeout time.Duration) (err error) { - expired := time.After(timeout) - name := strings.Split(kind, "/")[1] - - for { - // Delay check for 2 seconds - time.Sleep(time.Second * 2) - select { - case <-expired: - return fmt.Errorf("Timeout Expired") - default: - gvr, err := getGroupVersionResource(kind) - if err != nil { - return err - } - - resourceRule := &ResourceRule{ - Group: gvr.Group, - Version: gvr.Version, - Resource: gvr.Resource, - Namespaces: []string{namespace}, - Name: name, - } - - resources, err := GetResourcesDynamically(context.TODO(), resourceRule) - if err != nil { - return err - } - - if len(resources) > 0 { - // success - return nil - } - } + // Set timeout + timeoutString := waitPayload.Timeout + if timeoutString == "" { + timeoutString = "30s" } -} -// This is required bootstrapping for use of RunWait() -func WaitForCondition(condition string, namespace string, timeout string, args ...string) (err error) { - // Required for printer - investigate exposing this as needed for modification - ioStreams := genericiooptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} - o := cmd.KubectlOptions{ - IOStreams: ioStreams, - } - kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag().WithDiscoveryBurst(300).WithDiscoveryQPS(50.0) - // Namespace is attributed here - kubeConfigFlags.Namespace = &namespace - // Setup factory and flags - matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) - f := cmdutil.NewFactory(matchVersionKubeConfigFlags) - flags := wait.NewWaitFlags(f, o.IOStreams) - // Add condition - flags.ForCondition = condition - if timeout != "" { - flags.Timeout, err = time.ParseDuration(timeout) - if err != nil { - return err - } - } - opts, err := flags.ToOptions(args) - if err != nil { - return err - } - err = opts.RunWait() + // Timeout control parameters + duration, err := time.ParseDuration(timeoutString) if err != nil { - return err + return fmt.Errorf("invalid wait timeout: %s", timeoutString) } - return nil + waitCtx, waitCancel := context.WithTimeout(ctx, duration) + defer waitCancel() + + return pkgkubernetes.WaitForReady(waitCtx, cluster.watcher, []object.ObjMetadata{objMeta}) } diff --git a/src/test/e2e/composition_component_def_test.go b/src/test/e2e/composition_component_def_test.go index 05d19d40..f471b6e7 100644 --- a/src/test/e2e/composition_component_def_test.go +++ b/src/test/e2e/composition_component_def_test.go @@ -101,7 +101,7 @@ func TestComponentDefinitionComposition(t *testing.T) { t.Errorf("component definition is nil") } - composeResults, err := validate.ValidateOnCompDef(context.Background(), oscalModel.ComponentDefinition, "") + composeResults, err := validate.ValidateOnCompDef(ctx, oscalModel.ComponentDefinition, "") if err != nil { t.Error(err) } diff --git a/src/test/e2e/outputs_test.go b/src/test/e2e/outputs_test.go index 60c0a02a..45570d2f 100644 --- a/src/test/e2e/outputs_test.go +++ b/src/test/e2e/outputs_test.go @@ -57,7 +57,7 @@ func TestOutputs(t *testing.T) { components := *compDef.Components validationStore := validationstore.NewValidationStoreFromBackMatter(*compDef.BackMatter) - findingMap, observations, err := validate.ValidateOnControlImplementations(context.Background(), components[0].ControlImplementations, validationStore, "") + findingMap, observations, err := validate.ValidateOnControlImplementations(ctx, components[0].ControlImplementations, validationStore, "") if err != nil { t.Fatal(err) } diff --git a/src/test/e2e/scenarios/wait-field/oscal-component.yaml b/src/test/e2e/scenarios/wait-field/oscal-component.yaml index 0013f4d7..5926ba33 100644 --- a/src/test/e2e/scenarios/wait-field/oscal-component.yaml +++ b/src/test/e2e/scenarios/wait-field/oscal-component.yaml @@ -53,8 +53,9 @@ component-definition: resource: pods namespaces: [validation-test] wait: - condition: Ready - kind: pod/test-pod-wait + version: v1 + resource: pods + name: test-pod-wait namespace: validation-test timeout: 30s provider: diff --git a/src/test/e2e/scenarios/wait-field/validation.yaml b/src/test/e2e/scenarios/wait-field/validation.yaml index c69f08b4..9c858398 100644 --- a/src/test/e2e/scenarios/wait-field/validation.yaml +++ b/src/test/e2e/scenarios/wait-field/validation.yaml @@ -8,10 +8,10 @@ domain: version: v1 namespaces: [validation-test] wait: - condition: Ready - kind: pod/test-pod-wait + version: v1 + resource: pods + name: test-pod-wait namespace: validation-test - timeout: 30s provider: type: opa opa-spec: diff --git a/src/test/e2e/validation_composition_test.go b/src/test/e2e/validation_composition_test.go index 494b50c7..25f6d5b7 100644 --- a/src/test/e2e/validation_composition_test.go +++ b/src/test/e2e/validation_composition_test.go @@ -117,7 +117,7 @@ func validateComposition(ctx context.Context, t *testing.T, oscalPath, expectedF // Create a validation store from the back-matter if it exists validationStore := validationstore.NewValidationStoreFromBackMatter(*compDef.BackMatter) - findingMap, observations, err := validate.ValidateOnControlImplementations(context.Background(), components[0].ControlImplementations, validationStore, "") + findingMap, observations, err := validate.ValidateOnControlImplementations(ctx, components[0].ControlImplementations, validationStore, "") if err != nil { t.Fatalf("Error with validateOnControlImplementations: %v", err) }