diff --git a/go.mod b/go.mod index 7f5d0098b..2714f98c1 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/codeready-toolchain/toolchain-e2e require ( - github.com/codeready-toolchain/api v0.0.0-20241114213029-44333bf24bcf + github.com/codeready-toolchain/api v0.0.0-20241202100321-545317813297 github.com/codeready-toolchain/toolchain-common v0.0.0-20241114215157-a6a85252b2f5 github.com/davecgh/go-spew v1.1.1 github.com/fatih/color v1.15.0 github.com/ghodss/yaml v1.0.0 github.com/gofrs/uuid v3.3.0+incompatible - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/gorilla/websocket v1.4.2 github.com/gosuri/uiprogress v0.0.1 github.com/gosuri/uitable v0.0.4 @@ -101,13 +101,14 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.27.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index 63f84a1d6..23ccb03d0 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/codeready-toolchain/api v0.0.0-20241114213029-44333bf24bcf h1:tOHKd4PT6gnV8lLh3kmqqK9YONvL6oFKHpi0kGzfsvw= -github.com/codeready-toolchain/api v0.0.0-20241114213029-44333bf24bcf/go.mod h1:DUq1ffy9Mbersdgji48i/cm9Y+6NMwAdAQJNlfOrPRo= +github.com/codeready-toolchain/api v0.0.0-20241202100321-545317813297 h1:YsZesoSkP4sxGL7d1DtPHbqLGUe2A9srXVyW2IERKrw= +github.com/codeready-toolchain/api v0.0.0-20241202100321-545317813297/go.mod h1:DUq1ffy9Mbersdgji48i/cm9Y+6NMwAdAQJNlfOrPRo= github.com/codeready-toolchain/toolchain-common v0.0.0-20241114215157-a6a85252b2f5 h1:vW0C32c6sI9ZUGcUw3e9ftE9hqJ/bMo+TtRHp84Hung= github.com/codeready-toolchain/toolchain-common v0.0.0-20241114215157-a6a85252b2f5/go.mod h1:wx/d4HVbDPOadwpbxn28ZGClC5OmzelIK8p4wupDJVI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -301,8 +301,9 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v52 v52.0.0 h1:uyGWOY+jMQ8GVGSX8dkSwCzlehU3WfdxQ7GweO/JP7M= github.com/google/go-github/v52 v52.0.0/go.mod h1:WJV6VEEUPuMo5pXqqa2ZCZEdbQqua4zAk2MZTIo+m+4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -734,8 +735,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -830,8 +832,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -943,8 +945,9 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -955,8 +958,9 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -970,8 +974,9 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1041,7 +1046,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/test/e2e/parallel/nstemplatetier_test.go b/test/e2e/parallel/nstemplatetier_test.go index c2a3fc983..1ae806cff 100644 --- a/test/e2e/parallel/nstemplatetier_test.go +++ b/test/e2e/parallel/nstemplatetier_test.go @@ -9,6 +9,7 @@ import ( "time" v1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "github.com/gofrs/uuid" @@ -20,6 +21,7 @@ import ( . "github.com/codeready-toolchain/toolchain-e2e/testsupport/space" "github.com/codeready-toolchain/toolchain-e2e/testsupport/tiers" "github.com/codeready-toolchain/toolchain-e2e/testsupport/wait" + apiwait "k8s.io/apimachinery/pkg/util/wait" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -378,6 +380,88 @@ func TestFeatureToggles(t *testing.T) { }) } +func TestTierTemplateRevision(t *testing.T) { + t.Parallel() + + // given + awaitilities := WaitForDeployments(t) + hostAwait := awaitilities.Host() + // we create new NSTemplateTiers (derived from `base`) + baseTier, err := hostAwait.WaitForNSTemplateTier(t, "base1ns") + require.NoError(t, err) + // for the tiertemplaterevisions to be created the tiertemplates need to have template objects populated + // we add the RawExtension objects to the TemplateObjects field + crq := unstructured.Unstructured{Object: map[string]interface{}{ + "kind": "ClusterResourceQuota", + "metadata": map[string]interface{}{ + "name": "for-{{.SPACE_NAME}}-deployments", + }, + "spec": map[string]interface{}{ + "quota": map[string]interface{}{ + "hard": map[string]string{ + "count/deploymentconfigs.apps": "{{.DEPLOYMENT_QUOTA}}", + "count/deployments.apps": "{{.DEPLOYMENT_QUOTA}}", + "count/pods": "600", + }, + }, + "selector": map[string]interface{}{ + "annotations": map[string]string{}, + "labels": map[string]interface{}{ + "matchLabels": map[string]string{ + "toolchain.dev.openshift.com/space": "{{.SPACE_NAME}}", + }, + }, + }, + }, + }} + rawTemplateObjects := []runtime.RawExtension{{Object: &crq}} + updateTierTemplateObjects := func(template *toolchainv1alpha1.TierTemplate) error { + template.Spec.TemplateObjects = rawTemplateObjects + return nil + } + namespaceResourcesWithTemplateObjects := tiers.WithNamespaceResources(t, baseTier, updateTierTemplateObjects) + clusterResourcesWithTemplateObjects := tiers.WithClusterResources(t, baseTier, updateTierTemplateObjects) + spaceRolesWithTemplateObjects := tiers.WithSpaceRoles(t, baseTier, updateTierTemplateObjects) + tiers.CreateCustomNSTemplateTier(t, hostAwait, "ttr", baseTier, namespaceResourcesWithTemplateObjects, clusterResourcesWithTemplateObjects, spaceRolesWithTemplateObjects, tiers.WithParameter("DEPLOYMENT_QUOTA", "60")) + + // when + // we verify the counters in the status.history for 'tierUsingTierTemplateRevisions' tier + // and verify that TierTemplateRevision CRs were created, since all the tiertemplates now have templateObjects field populated + customTier, err := hostAwait.WaitForNSTemplateTierAndCheckTemplates(t, "ttr", wait.HasStatusTierTemplateRevisions([]string{ + fmt.Sprintf("ttrfrom%s", baseTier.Spec.Namespaces[0].TemplateRef), // check that the revision field is set using the expected tierTemplate refs as keys + fmt.Sprintf("ttrfrom%s", baseTier.Spec.SpaceRoles["admin"].TemplateRef), // we can safely use the template refs from base tier since the custom tier was created from base one. + fmt.Sprintf("ttrfrom%s", baseTier.Spec.ClusterResources.TemplateRef), + })) + require.NoError(t, err) + + // then + // check the expected total number of ttr matches + err = apiwait.PollUntilContextTimeout(context.TODO(), hostAwait.RetryInterval, hostAwait.Timeout, true, func(ctx context.Context) (done bool, err error) { + objs := &toolchainv1alpha1.TierTemplateRevisionList{} + if err := hostAwait.Client.List(ctx, objs, client.InNamespace(hostAwait.Namespace)); err != nil { + return false, err + } + // we IDEALLY expect one TTR per each tiertemplate to be created (clusterresource, namespace and spacerole), thus a total of 3 TTRs ideally. + // But since the creation of a TTR could be very quick and could trigger another reconcile of the NSTemplateTier before the status is actually updated with the reference, + // this might generate some copies of the TTRs. This is not a problem in production since the cleanup mechanism of TTRs will remove the extra ones but could cause some flakiness with the test, + // thus we assert the number of TTRs doesn't exceed the double of the expected number. + assert.LessOrEqual(t, len(objs.Items), 6) + // we check that the TTR content has the parameters replaced with values from the NSTemplateTier + for _, obj := range objs.Items { + // the object should have all the variables still there since this one will be replaced when provisioning the Space + assert.Contains(t, string(obj.Spec.TemplateObjects[0].Raw), ".SPACE_NAME") + assert.Contains(t, string(obj.Spec.TemplateObjects[0].Raw), ".DEPLOYMENT_QUOTA") + // the parameter is copied from the NSTemplateTier + assert.NotNil(t, obj.Spec.Parameters) + assert.NotNil(t, customTier.Spec.Parameters) + assert.Equal(t, obj.Spec.Parameters[0].Name, customTier.Spec.Parameters[0].Name) + assert.Equal(t, obj.Spec.Parameters[0].Value, customTier.Spec.Parameters[0].Value) + } + return true, nil + }) + require.NoError(t, err) +} + func withClusterRoleBindings(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier, feature string) tiers.CustomNSTemplateTierModifier { clusterRB := getCRBforFeature(t, feature) // This is the ClusterRoleBinding for the desired feature noiseCRB := getCRBforFeature(t, unknownFeature) // This is a noise CRB for unknown/disabled feature. To be used to check that this CRB is never created. diff --git a/testsupport/cleanup/clean.go b/testsupport/cleanup/clean.go index 967f1df09..ea680a824 100644 --- a/testsupport/cleanup/clean.go +++ b/testsupport/cleanup/clean.go @@ -106,6 +106,7 @@ func (c *cleanTask) cleanObject() { objToClean, ok := c.objToClean.DeepCopyObject().(client.Object) require.True(c.t, ok) userSignup, isUserSignup := c.objToClean.(*toolchainv1alpha1.UserSignup) + nsTemplateTier, isNsTemplateTier := c.objToClean.(*toolchainv1alpha1.NSTemplateTier) kind := objToClean.GetObjectKind().GroupVersionKind().Kind if kind == "" { kind = reflect.TypeOf(c.objToClean).Elem().Name() @@ -126,10 +127,13 @@ func (c *cleanTask) cleanObject() { } } } + // if the object was NSTemplateTier, then let's check that the TierTemplateRevisions were deleted as well + _, err := c.verifyTierTemplateRevisionsDeleted(isNsTemplateTier, nsTemplateTier, true) + require.NoError(c.t, err) // wait until deletion is done c.t.Logf("waiting until %s: %s is completely deleted", kind, objToClean.GetName()) - err := wait.PollUntilContextTimeout(context.TODO(), defaultRetryInterval, defaultTimeout, true, func(ctx context.Context) (done bool, err error) { + err = wait.PollUntilContextTimeout(context.TODO(), defaultRetryInterval, defaultTimeout, true, func(ctx context.Context) (done bool, err error) { if err := c.client.Get(context.TODO(), test.NamespacedName(objToClean.GetNamespace(), objToClean.GetName()), objToClean); err != nil { if errors.IsNotFound(err) { // if the object was UserSignup, then let's check that the MUR is deleted as well @@ -140,6 +144,10 @@ func (c *cleanTask) cleanObject() { if spaceDeleted, err := c.verifySpaceDeleted(isUserSignup, userSignup, false); !spaceDeleted || err != nil { return false, err } + // if the object was NSTemplateTier, then let's check that the TTRs were deleted as well + if ttrsDeleted, err := c.verifyTierTemplateRevisionsDeleted(isNsTemplateTier, nsTemplateTier, false); !ttrsDeleted || err != nil { + return false, err + } return true, nil } c.t.Logf("problem with getting the related %s '%s': %s", kind, objToClean.GetName(), err) @@ -240,3 +248,37 @@ func (c *cleanTask) verifySpaceDeleted(isUserSignup bool, userSignup *toolchainv } return true, nil } + +func (c *cleanTask) verifyTierTemplateRevisionsDeleted(isNsTemplateTier bool, nsTemplateTier *toolchainv1alpha1.NSTemplateTier, delete bool) (bool, error) { + if !isNsTemplateTier { + return true, nil + } + ttrs := &toolchainv1alpha1.TierTemplateRevisionList{} + if err := c.client.List(context.TODO(), ttrs, + client.InNamespace(nsTemplateTier.GetNamespace()), + client.MatchingLabels{toolchainv1alpha1.TierLabelKey: nsTemplateTier.GetName()}); err != nil { + c.t.Logf("problem with getting the ttrs for tier %s: %s", nsTemplateTier.GetName(), err) + return false, err + } + if len(ttrs.Items) == 0 { + c.t.Logf("the NSTemplateTier %s doesn't have TTRs", nsTemplateTier.GetName()) + return true, nil + } + if delete { + for i := range ttrs.Items { + ttr := ttrs.Items[i] + c.t.Logf("deleting also the related TTR %s", ttr.GetName()) + if err := c.client.Delete(context.TODO(), &ttr, propagationPolicyOpts); err != nil { + if errors.IsNotFound(err) { + continue + } + c.t.Logf("problem with deleting the related TTR %s: %s", ttr.GetName(), err) + return false, err + } + } + } else if len(ttrs.Items) > 0 { + // ttrs are still there + return false, nil + } + return true, nil +} diff --git a/testsupport/tiers/tier_setup.go b/testsupport/tiers/tier_setup.go index e56676104..fb7130b1a 100644 --- a/testsupport/tiers/tier_setup.go +++ b/testsupport/tiers/tier_setup.go @@ -47,13 +47,13 @@ func WithClusterResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateT } } -func WithNamespaceResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier) CustomNSTemplateTierModifier { +func WithNamespaceResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier, modifiers ...TierTemplateModifier) CustomNSTemplateTierModifier { return func(hostAwait *HostAwaitility, tier *CustomNSTemplateTier) error { tier.NamespaceResourcesTier = otherTier // configure the "wrapped" NSTemplateTier tier.Spec.Namespaces = make([]toolchainv1alpha1.NSTemplateTierNamespace, len(otherTier.Spec.Namespaces)) for i, def := range otherTier.Spec.Namespaces { - tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef) + tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef, modifiers...) if err != nil { return err } @@ -63,13 +63,13 @@ func WithNamespaceResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplat } } -func WithSpaceRoles(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier) CustomNSTemplateTierModifier { +func WithSpaceRoles(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier, modifiers ...TierTemplateModifier) CustomNSTemplateTierModifier { return func(hostAwait *HostAwaitility, tier *CustomNSTemplateTier) error { tier.SpaceRolesTier = otherTier // configure the "wrapped" NSTemplateTier tier.Spec.SpaceRoles = make(map[string]toolchainv1alpha1.NSTemplateTierSpaceRole, len(otherTier.Spec.SpaceRoles)) for name, def := range otherTier.Spec.SpaceRoles { - tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef) + tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef, modifiers...) if err != nil { return err } @@ -81,6 +81,21 @@ func WithSpaceRoles(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier) C } } +func WithParameter(name, value string) CustomNSTemplateTierModifier { + return func(hostAwait *HostAwaitility, tier *CustomNSTemplateTier) error { + if tier.Spec.Parameters == nil { + tier.Spec.Parameters = []toolchainv1alpha1.Parameter{} + } + tier.Spec.Parameters = append(tier.Spec.Parameters, + toolchainv1alpha1.Parameter{ + Name: name, + Value: value, + }, + ) + return nil + } +} + // CreateCustomNSTemplateTier creates a custom tier. // If no modifiers provided then the new tier will use copies of the baseTier cluster, namespace and space roles templates // without any modifications. diff --git a/testsupport/wait/host.go b/testsupport/wait/host.go index fdd09019e..c67b971bf 100644 --- a/testsupport/wait/host.go +++ b/testsupport/wait/host.go @@ -1023,18 +1023,61 @@ func (a *HostAwaitility) WaitForNSTemplateTierAndCheckTemplates(t *testing.T, na if ns.TemplateRef == "" { return nil, fmt.Errorf("missing 'templateRef' in namespace #%d in NSTemplateTier '%s'", i, tier.Name) } - if _, err := a.WaitForTierTemplate(t, ns.TemplateRef); err != nil { + tierTemplateNamespaces, err := a.WaitForTierTemplate(t, ns.TemplateRef) + if err != nil { return nil, err } + // if the tier template supports Tier Template Revisions then let's check those + if tierTemplateNamespaces.Spec.TemplateObjects != nil { + ttrName, found := tier.Status.Revisions[tierTemplateNamespaces.GetName()] + if !found { + return nil, fmt.Errorf("missing revision for TierTemplate %s in NSTemplateTier '%s'", tierTemplateNamespaces.GetName(), tier.Name) + } + if _, err := a.WaitForTierTemplateRevision(t, ttrName); err != nil { + return nil, err + } + } } if tier.Spec.ClusterResources != nil { if tier.Spec.ClusterResources.TemplateRef == "" { return nil, fmt.Errorf("missing 'templateRef' for the cluster resources in NSTemplateTier '%s'", tier.Name) } - if _, err := a.WaitForTierTemplate(t, tier.Spec.ClusterResources.TemplateRef); err != nil { + tierTemplateClusterResources, err := a.WaitForTierTemplate(t, tier.Spec.ClusterResources.TemplateRef) + if err != nil { return nil, err } + // if the tier template supports Tier Template Revisions then let's check those + if tierTemplateClusterResources.Spec.TemplateObjects != nil { + ttrName, found := tier.Status.Revisions[tierTemplateClusterResources.GetName()] + if !found { + return nil, fmt.Errorf("missing revision for TierTemplate %s in NSTemplateTier '%s'", tierTemplateClusterResources.GetName(), tier.Name) + } + if _, err := a.WaitForTierTemplateRevision(t, ttrName); err != nil { + return nil, err + } + } + } + + for _, r := range tier.Spec.SpaceRoles { + if r.TemplateRef == "" { + return nil, fmt.Errorf("missing 'templateRef' in spaceRole %s in NSTemplateTier '%s'", r.TemplateRef, tier.Name) + } + tierTemplateSpaceRoles, err := a.WaitForTierTemplate(t, r.TemplateRef) + if err != nil { + return nil, err + } + // if the tier template supports Tier Template Revisions then let's check those + if tierTemplateSpaceRoles.Spec.TemplateObjects != nil { + ttrName, found := tier.Status.Revisions[tierTemplateSpaceRoles.GetName()] + if !found { + return nil, fmt.Errorf("missing revision for TierTemplate %s in NSTemplateTier '%s'", tierTemplateSpaceRoles.GetName(), tier.Name) + } + if _, err := a.WaitForTierTemplateRevision(t, ttrName); err != nil { + return nil, err + } + } } + return tier, err } @@ -1061,6 +1104,69 @@ func (a *HostAwaitility) WaitForTierTemplate(t *testing.T, name string) (*toolch return tierTemplate, err } +// TierTemplateRevisionWaitCriterion a struct to compare with an expected TierTemplateRevision +type TierTemplateRevisionWaitCriterion struct { + Match func(*toolchainv1alpha1.TierTemplateRevision) bool + Diff func(*toolchainv1alpha1.TierTemplateRevision) string +} + +func matchTierTemplateRevisionWaitCriterion(actual *toolchainv1alpha1.TierTemplateRevision, criteria ...TierTemplateRevisionWaitCriterion) bool { + for _, c := range criteria { + // if at least one criteria does not match, keep waiting + if !c.Match(actual) { + return false + } + } + return true +} + +func (a *HostAwaitility) printTierTemplateRevisionWaitCriterionDiffs(t *testing.T, actual *toolchainv1alpha1.TierTemplateRevision, criteria ...TierTemplateRevisionWaitCriterion) { + buf := &strings.Builder{} + if actual == nil { + buf.WriteString("failed to find TierTemplateRevision\n") + } else { + buf.WriteString("failed to find TierTemplateRevision with matching criteria:\n") + buf.WriteString("actual:\n") + y, _ := StringifyObject(actual) + buf.Write(y) + buf.WriteString("\n----\n") + buf.WriteString("diffs:\n") + for _, c := range criteria { + if !c.Match(actual) { + buf.WriteString(c.Diff(actual)) + buf.WriteString("\n") + } + } + } + // include also all TierTemplateRevisions in the host namespace, to help troubleshooting + a.listAndPrint(t, "TierTemplateRevisions", a.Namespace, &toolchainv1alpha1.TierTemplateRevisionList{}) + + t.Log(buf.String()) +} + +// WaitForTierTemplateRevision waits until a TierTemplateRevision with the given labels to exists +// Returns an error if the resource did not exist (or something wrong happened) +func (a *HostAwaitility) WaitForTierTemplateRevision(t *testing.T, ttrName string, criteria ...TierTemplateRevisionWaitCriterion) (*toolchainv1alpha1.TierTemplateRevision, error) { // nolint:unparam + ttr := &toolchainv1alpha1.TierTemplateRevision{} + t.Logf("waiting until TierTemplateRevision with name '%s' exists in namespace '%s'...", ttrName, a.Namespace) + err := wait.PollUntilContextTimeout(context.TODO(), a.RetryInterval, a.Timeout, true, func(ctx context.Context) (done bool, err error) { + err = a.Client.Get(ctx, types.NamespacedName{ + Namespace: a.Namespace, + Name: ttrName, + }, ttr) + // no match found, print the diffs + if err != nil { + return false, err + } + return matchTierTemplateRevisionWaitCriterion(ttr, criteria...), nil + }) + // log message if an error occurred + if err != nil { + a.printTierTemplateRevisionWaitCriterionDiffs(t, ttr, criteria...) + } + return ttr, err +} + // NSTemplateTierWaitCriterion a struct to compare with an expected NSTemplateTier type NSTemplateTierWaitCriterion struct { Match func(*toolchainv1alpha1.NSTemplateTier) bool @@ -1131,6 +1237,26 @@ func UntilNSTemplateTierStatusUpdates(expected int) NSTemplateTierWaitCriterion } } +// HasStatusTierTemplateRevisions verifies revisions for the given TierTemplates are set in the `NSTemplateTier.Status.Revisions` +func HasStatusTierTemplateRevisions(revisions []string) NSTemplateTierWaitCriterion { + return NSTemplateTierWaitCriterion{ + Match: func(actual *toolchainv1alpha1.NSTemplateTier) bool { + if len(actual.Status.Revisions) != len(revisions) { + return false + } + for _, tierTemplateRef := range revisions { + if _, found := actual.Status.Revisions[tierTemplateRef]; !found { + return false + } + } + return true + }, + Diff: func(actual *toolchainv1alpha1.NSTemplateTier) string { + return fmt.Sprintf("expected revision keys %v not found in: %v", revisions, actual.Status.Revisions) + }, + } +} + // HasNoTemplateRefWithSuffix checks that ALL namespaces' `TemplateRef` doesn't have the suffix func HasNoTemplateRefWithSuffix(suffix string) NSTemplateTierSpecMatcher { return NSTemplateTierSpecMatcher{