Applyset dry run tests + ID value (#116265)
* Test for ApplySet with --dry-run=client|server * Use the real format for ApplySet ID * Incorporate feedback * Adjustments from rebase Kubernetes-commit: 6a31757f45693fec5ea4723bcb405ce4437e31ca
This commit is contained in:
parent
c22c2cf969
commit
ca98377c3e
8
go.mod
8
go.mod
|
@ -30,10 +30,10 @@ require (
|
|||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/sys v0.5.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.0.0-20230314010636-4c844db47d54
|
||||
k8s.io/api v0.0.0-20230314091508-112a65bae227
|
||||
k8s.io/apimachinery v0.0.0-20230314010357-128166500c57
|
||||
k8s.io/cli-runtime v0.0.0-20230314020252-2497f10db39b
|
||||
k8s.io/client-go v0.0.0-20230314011018-471f66fb1055
|
||||
k8s.io/client-go v0.0.0-20230315061813-3cafc13f5d42
|
||||
k8s.io/component-base v0.0.0-20230313212246-ce16dede9c0e
|
||||
k8s.io/component-helpers v0.0.0-20230313212358-9baf6e0e627d
|
||||
k8s.io/klog/v2 v2.90.1
|
||||
|
@ -91,10 +91,10 @@ require (
|
|||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.0.0-20230314010636-4c844db47d54
|
||||
k8s.io/api => k8s.io/api v0.0.0-20230314091508-112a65bae227
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230314010357-128166500c57
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20230314020252-2497f10db39b
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20230314011018-471f66fb1055
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20230315061813-3cafc13f5d42
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20230314010121-667a115fdda8
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20230313212246-ce16dede9c0e
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20230313212358-9baf6e0e627d
|
||||
|
|
8
go.sum
8
go.sum
|
@ -531,14 +531,14 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.0.0-20230314010636-4c844db47d54 h1:0VSpq40qYJx9GN/G+BzJiVgcz3cR3xPc+xn3nQLAoZ8=
|
||||
k8s.io/api v0.0.0-20230314010636-4c844db47d54/go.mod h1:YsFNxBfPYQZAIBg0XvL94rxDDVZvQQQUlo4KbwEEqNU=
|
||||
k8s.io/api v0.0.0-20230314091508-112a65bae227 h1:Ak4YrHI4101ZMfx2hkXnp//d5r0nKXA8RkNaHCBJ7MA=
|
||||
k8s.io/api v0.0.0-20230314091508-112a65bae227/go.mod h1:YsFNxBfPYQZAIBg0XvL94rxDDVZvQQQUlo4KbwEEqNU=
|
||||
k8s.io/apimachinery v0.0.0-20230314010357-128166500c57 h1:Vr1geeI+at1NNCWyTN70NtPSNcveZ+fAcbZivzwHknM=
|
||||
k8s.io/apimachinery v0.0.0-20230314010357-128166500c57/go.mod h1:1AlvkfXatlv5Kq9dCZg3Ksdu/DyrZ31Q0CncRqQ8Q9I=
|
||||
k8s.io/cli-runtime v0.0.0-20230314020252-2497f10db39b h1:Q2m8dkuvdn9kQspNZGR10Yr8gjdQDOLdLgHLI3FcNG4=
|
||||
k8s.io/cli-runtime v0.0.0-20230314020252-2497f10db39b/go.mod h1:0uP53DPxCWJvpFo91n4cTHSqkKGzx+PDunpv8Ic2EXg=
|
||||
k8s.io/client-go v0.0.0-20230314011018-471f66fb1055 h1:J6JKsnjJepE9WwPlTuU5To88f7hkMzSHf72W8kjfEZY=
|
||||
k8s.io/client-go v0.0.0-20230314011018-471f66fb1055/go.mod h1:2/gjZKF6uneNXNF3xsgmEUFbqNjlhWprJl4wv4CNd0c=
|
||||
k8s.io/client-go v0.0.0-20230315061813-3cafc13f5d42 h1:b7qY4Bq8gHunC2mYT/RAtMWDgMmkMb16oQuktJkHpHI=
|
||||
k8s.io/client-go v0.0.0-20230315061813-3cafc13f5d42/go.mod h1:oWqDJxiXYcSV7S8woQ4H5epopeK85SJyOu5lJsMndcU=
|
||||
k8s.io/component-base v0.0.0-20230313212246-ce16dede9c0e h1:/mftAl/78q8dPZU8YkshNzt1XpbEJTGsxYZP/WGI4og=
|
||||
k8s.io/component-base v0.0.0-20230313212246-ce16dede9c0e/go.mod h1:MQKfc/tXtS50042g1VxMb2W2E8PCt97xO8RsLTw3AeI=
|
||||
k8s.io/component-helpers v0.0.0-20230313212358-9baf6e0e627d h1:c3f8fiRpmkt5PQw4lszj1F0CcMDOMqQsMy0zRInmw8o=
|
||||
|
|
|
@ -313,7 +313,6 @@ func (flags *ApplyFlags) ToOptions(f cmdutil.Factory, cmd *cobra.Command, baseNa
|
|||
if enforceNamespace && parent.IsNamespaced() {
|
||||
parent.Namespace = namespace
|
||||
}
|
||||
// TODO: is version.Get() the right thing? Does it work for non-kubectl package consumers?
|
||||
tooling := ApplySetTooling{name: baseName, version: ApplySetToolVersion}
|
||||
restClient, err := f.ClientForMapping(parent.RESTMapping)
|
||||
if err != nil || restClient == nil {
|
||||
|
@ -419,8 +418,6 @@ func (o *ApplyOptions) Validate() error {
|
|||
return fmt.Errorf("--selector is incompatible with --applyset")
|
||||
} else if len(o.PruneResources) > 0 {
|
||||
return fmt.Errorf("--prune-allowlist is incompatible with --applyset")
|
||||
} else {
|
||||
klog.Warning("WARNING: --prune --applyset is not fully implemented and does not yet prune any resources.")
|
||||
}
|
||||
} else {
|
||||
if !o.All && o.Selector == "" {
|
||||
|
|
|
@ -2435,7 +2435,7 @@ metadata:
|
|||
applyset.k8s.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.k8s.io/id: placeholder-todo
|
||||
applyset.k8s.io/id: applyset-nqNkDlL072a9O3FBtGMDroXnF18TNtgUetAA6vsaglI-v1
|
||||
name: mySet
|
||||
namespace: test
|
||||
`, string(createdSecret))
|
||||
|
@ -2465,7 +2465,7 @@ metadata:
|
|||
applyset.k8s.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.k8s.io/id: placeholder-todo
|
||||
applyset.k8s.io/id: applyset-nqNkDlL072a9O3FBtGMDroXnF18TNtgUetAA6vsaglI-v1
|
||||
name: mySet
|
||||
namespace: test
|
||||
`, string(updatedSecret))
|
||||
|
@ -2496,7 +2496,7 @@ metadata:
|
|||
applyset.k8s.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.k8s.io/id: placeholder-todo
|
||||
applyset.k8s.io/id: applyset-nqNkDlL072a9O3FBtGMDroXnF18TNtgUetAA6vsaglI-v1
|
||||
name: mySet
|
||||
namespace: test
|
||||
`, string(updatedSecret))
|
||||
|
@ -2527,7 +2527,7 @@ metadata:
|
|||
applyset.k8s.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.k8s.io/id: placeholder-todo
|
||||
applyset.k8s.io/id: applyset-nqNkDlL072a9O3FBtGMDroXnF18TNtgUetAA6vsaglI-v1
|
||||
name: mySet
|
||||
namespace: test
|
||||
`, string(updatedSecret))
|
||||
|
@ -2577,7 +2577,7 @@ func TestApplySetInvalidLiveParent(t *testing.T) {
|
|||
}),
|
||||
}
|
||||
}
|
||||
validIDLabel := "placeholder-todo"
|
||||
validIDLabel := "applyset-nqNkDlL072a9O3FBtGMDroXnF18TNtgUetAA6vsaglI-v1"
|
||||
validToolingAnnotation := "kubectl/v1.27.0"
|
||||
validGrsAnnotation := "deployments.apps,namespaces,secrets"
|
||||
|
||||
|
@ -2866,7 +2866,7 @@ metadata:
|
|||
applyset.k8s.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.k8s.io/id: placeholder-todo
|
||||
applyset.k8s.io/id: applyset-nqNkDlL072a9O3FBtGMDroXnF18TNtgUetAA6vsaglI-v1
|
||||
name: mySet
|
||||
namespace: test
|
||||
`
|
||||
|
@ -3147,3 +3147,75 @@ func fatalNoExit(t *testing.T, ioStreams genericclioptions.IOStreams) func(msg s
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplySetDryRun(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
nameRC, rc := readReplicationController(t, filenameRC)
|
||||
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
|
||||
nameParentSecret := "mySet"
|
||||
pathSecret := "/namespaces/test/secrets/" + nameParentSecret
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
// Scenario: the rc 'exists' server side but the applyset secret does not
|
||||
// In dry run mode, non-dry run patch requests should not be made, and the secret should not be created
|
||||
serverSideData := map[string][]byte{
|
||||
pathRC: rc,
|
||||
}
|
||||
fakeDryRunClient := func(t *testing.T, allowPatch bool) *fake.RESTClient {
|
||||
return &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "GET" {
|
||||
data, ok := serverSideData[req.URL.Path]
|
||||
if !ok {
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader(nil))}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader(data))}, nil
|
||||
}
|
||||
if req.Method == "PATCH" && allowPatch && req.URL.Query().Get("dryRun") == "All" {
|
||||
data, err := io.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader(data))}, nil
|
||||
}
|
||||
|
||||
t.Fatalf("unexpected request: to %s\n%#v", req.URL.Path, req)
|
||||
return nil, nil
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("server side dry run", func(t *testing.T) {
|
||||
ioStreams, _, outbuff, _ := genericclioptions.NewTestIOStreams()
|
||||
tf.Client = fakeDryRunClient(t, true)
|
||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.ApplySet}, t, func(t *testing.T) {
|
||||
cmd := NewCmdApply("kubectl", tf, ioStreams)
|
||||
cmd.Flags().Set("filename", filenameRC)
|
||||
cmd.Flags().Set("server-side", "true")
|
||||
cmd.Flags().Set("applyset", nameParentSecret)
|
||||
cmd.Flags().Set("prune", "true")
|
||||
cmd.Flags().Set("dry-run", "server")
|
||||
cmd.Run(cmd, []string{})
|
||||
})
|
||||
assert.Equal(t, "replicationcontroller/test-rc serverside-applied (server dry run)\n", outbuff.String())
|
||||
assert.Equal(t, len(serverSideData), 1, "unexpected creation")
|
||||
require.Nil(t, serverSideData[pathSecret], "secret was created")
|
||||
})
|
||||
|
||||
t.Run("client side dry run", func(t *testing.T) {
|
||||
ioStreams, _, outbuff, _ := genericclioptions.NewTestIOStreams()
|
||||
tf.Client = fakeDryRunClient(t, false)
|
||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.ApplySet}, t, func(t *testing.T) {
|
||||
cmd := NewCmdApply("kubectl", tf, ioStreams)
|
||||
cmd.Flags().Set("filename", filenameRC)
|
||||
cmd.Flags().Set("applyset", nameParentSecret)
|
||||
cmd.Flags().Set("prune", "true")
|
||||
cmd.Flags().Set("dry-run", "client")
|
||||
cmd.Run(cmd, []string{})
|
||||
})
|
||||
assert.Equal(t, "replicationcontroller/test-rc configured (dry run)\n", outbuff.String())
|
||||
assert.Equal(t, len(serverSideData), 1, "unexpected creation")
|
||||
require.Nil(t, serverSideData[pathSecret], "secret was created")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||
package apply
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
@ -59,10 +61,15 @@ const (
|
|||
ApplySetGRsAnnotation = "applyset.k8s.io/contains-group-resources"
|
||||
|
||||
// ApplySetParentIDLabel is the key of the label that makes object an ApplySet parent object.
|
||||
// Its value MUST be the base64 encoding of the hash of the GKNN of the object it is on,
|
||||
// in the form base64(sha256(<name>.<namespace>.<kind>.<group>)), using the URL safe encoding of RFC4648.
|
||||
// Its value MUST use the format specified in V1ApplySetIdFormat below
|
||||
ApplySetParentIDLabel = "applyset.k8s.io/id"
|
||||
|
||||
// V1ApplySetIdFormat is the format required for the value of ApplySetParentIDLabel (and ApplysetPartOfLabel).
|
||||
// The %s segment is the unique ID of the object itself, which MUST be the base64 encoding
|
||||
// (using the URL safe encoding of RFC4648) of the hash of the GKNN of the object it is on, in the form:
|
||||
// base64(sha256(<name>.<namespace>.<kind>.<group>)).
|
||||
V1ApplySetIdFormat = "applyset-%s-v1"
|
||||
|
||||
// ApplysetPartOfLabel is the key of the label which indicates that the object is a member of an ApplySet.
|
||||
// The value of the label MUST match the value of ApplySetParentIDLabel on the parent object.
|
||||
ApplysetPartOfLabel = "applyset.k8s.io/part-of"
|
||||
|
@ -141,10 +148,17 @@ func NewApplySet(parent *ApplySetParentRef, tooling ApplySetTooling, mapper meta
|
|||
}
|
||||
}
|
||||
|
||||
const applySetIDPartDelimiter = "."
|
||||
|
||||
// ID is the label value that we are using to identify this applyset.
|
||||
// Format: base64(sha256(<name>.<namespace>.<kind>.<group>)), using the URL safe encoding of RFC4648.
|
||||
|
||||
func (a ApplySet) ID() string {
|
||||
// TODO: base64(sha256(gknn))
|
||||
return "placeholder-todo"
|
||||
unencoded := strings.Join([]string{a.parentRef.Name, a.parentRef.Namespace, a.parentRef.GroupVersionKind.Kind, a.parentRef.GroupVersionKind.Group}, applySetIDPartDelimiter)
|
||||
hashed := sha256.Sum256([]byte(unencoded))
|
||||
b64 := base64.RawURLEncoding.EncodeToString(hashed[:])
|
||||
// Label values must start and end with alphanumeric values, so add a known-safe prefix and suffix.
|
||||
return fmt.Sprintf(V1ApplySetIdFormat, b64)
|
||||
}
|
||||
|
||||
// Validate imposes restrictions on the parent object that is used to track the applyset.
|
||||
|
@ -413,7 +427,7 @@ func generateResourcesAnnotation(resources sets.Set[schema.GroupVersionResource]
|
|||
}
|
||||
|
||||
func (a ApplySet) FieldManager() string {
|
||||
return fmt.Sprintf("%s-applyset-%s", a.toolingID.name, a.ID()) // TODO: validate this choice
|
||||
return fmt.Sprintf("%s-applyset", a.toolingID.name)
|
||||
}
|
||||
|
||||
// ParseApplySetParentRef creates a new ApplySetParentRef from a parent reference in the format [RESOURCE][.GROUP]/NAME
|
||||
|
|
|
@ -2,7 +2,7 @@ apiVersion: v1
|
|||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
applyset.k8s.io/part-of: placeholder-todo
|
||||
applyset.k8s.io/part-of: applyset-bjd1LnyQq0mtUu-riZCqjDQOmh0iNb9O2RcuT12WR0k-v1
|
||||
name: foo
|
||||
|
||||
---
|
||||
|
@ -11,5 +11,5 @@ apiVersion: v1
|
|||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
applyset.k8s.io/part-of: placeholder-todo
|
||||
applyset.k8s.io/part-of: applyset-bjd1LnyQq0mtUu-riZCqjDQOmh0iNb9O2RcuT12WR0k-v1
|
||||
name: bar
|
||||
|
|
|
@ -2,5 +2,5 @@ apiVersion: v1
|
|||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
applyset.k8s.io/part-of: placeholder-todo
|
||||
applyset.k8s.io/part-of: applyset-bjd1LnyQq0mtUu-riZCqjDQOmh0iNb9O2RcuT12WR0k-v1
|
||||
name: foo
|
||||
|
|
Loading…
Reference in New Issue