diff --git a/test/integration/certificates/BUILD.bazel b/test/integration/certificates/BUILD.bazel index b45e90c8c..10d772464 100644 --- a/test/integration/certificates/BUILD.bazel +++ b/test/integration/certificates/BUILD.bazel @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test") go_test( name = "go_default_test", srcs = [ + "condition_list_type_test.go", "issuing_controller_test.go", "metrics_controller_test.go", "revisionmanager_controller_test.go", @@ -22,16 +23,20 @@ go_test( "//pkg/controller/certificates/trigger:go_default_library", "//pkg/logs:go_default_library", "//pkg/metrics:go_default_library", + "//pkg/util:go_default_library", "//pkg/util/feature:go_default_library", "//pkg/util/pki:go_default_library", "//test/integration/framework:go_default_library", "//test/unit/gen:go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_apimachinery//pkg/types:go_default_library", "@io_k8s_apimachinery//pkg/util/wait:go_default_library", "@io_k8s_component_base//featuregate/testing:go_default_library", "@io_k8s_utils//clock:go_default_library", "@io_k8s_utils//clock/testing:go_default_library", + "@io_k8s_utils//pointer:go_default_library", ], ) diff --git a/test/integration/certificates/condition_list_type_test.go b/test/integration/certificates/condition_list_type_test.go new file mode 100644 index 000000000..3db6bcb98 --- /dev/null +++ b/test/integration/certificates/condition_list_type_test.go @@ -0,0 +1,160 @@ +/* +Copyright 2020 The cert-manager Authors. + +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 certificates + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apitypes "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" + "github.com/jetstack/cert-manager/pkg/util" + "github.com/jetstack/cert-manager/test/integration/framework" +) + +// Test_ConditionsListType ensures that the Certificate's Conditions API field +// has been labelled as a list map. This is so that district field managers are +// able to add and modify different Conditions without disrupting each others +// entries. Conditions are keyed by `Type`. +func Test_ConditionsListType(t *testing.T) { + const ( + namespace = "test-condition-list-type" + name = "test-condition-list-type" + ) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) + defer cancel() + + restConfig, stopFn := framework.RunControlPlane(t, ctx) + defer stopFn() + + // Build clients with different field managers. + aliceRestConfig := util.RestConfigWithUserAgent(restConfig, "alice") + aliceFieldManager := util.PrefixFromUserAgent(aliceRestConfig.UserAgent) + aliceKubeClient, _, aliceCMClient, _ := framework.NewClients(t, aliceRestConfig) + + bobRestConfig := util.RestConfigWithUserAgent(restConfig, "bob") + bobFieldManager := util.PrefixFromUserAgent(bobRestConfig.UserAgent) + _, _, bobCMClient, _ := framework.NewClients(t, bobRestConfig) + + t.Log("creating test Namespace") + ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + _, err := aliceKubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + assert.NoError(t, err) + + t.Log("creating empty Certificate") + crt := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, + Spec: cmapi.CertificateSpec{ + CommonName: "test", SecretName: "test", IssuerRef: cmmeta.ObjectReference{Name: "test"}, + }, + } + _, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) + assert.NoError(t, err) + + t.Log("ensuring alice can set Ready condition") + crt = &cmapi.Certificate{ + TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, + Status: cmapi.CertificateStatus{ + Conditions: []cmapi.CertificateCondition{{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, + }, + } + crtData, err := json.Marshal(crt) + assert.NoError(t, err) + _, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Patch( + ctx, name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: aliceFieldManager}, "status", + ) + assert.NoError(t, err) + + t.Log("ensuring bob can set a district issuing condition, without changing the ready condition") + crt = &cmapi.Certificate{ + TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, + Status: cmapi.CertificateStatus{ + Conditions: []cmapi.CertificateCondition{{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, + }, + } + crtData, err = json.Marshal(crt) + assert.NoError(t, err) + _, err = bobCMClient.CertmanagerV1().Certificates(namespace).Patch( + ctx, name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: bobFieldManager}, "status", + ) + assert.NoError(t, err) + + crt, err = bobCMClient.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) + assert.NoError(t, err) + assert.Equal(t, []cmapi.CertificateCondition{ + {Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}, + {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}, + }, crt.Status.Conditions, "conditions did not match the expected 2 distinct condition types") + + t.Log("alice should override an existing condition by another manager, and can delete an existing owned condition type through omission") + crt = &cmapi.Certificate{ + TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, + Status: cmapi.CertificateStatus{ + Conditions: []cmapi.CertificateCondition{{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}}, + }, + } + crtData, err = json.Marshal(crt) + assert.NoError(t, err) + _, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Patch( + ctx, name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: aliceFieldManager}, "status", + ) + assert.NoError(t, err) + + crt, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) + assert.NoError(t, err) + assert.Equal(t, []cmapi.CertificateCondition{ + {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, + }, crt.Status.Conditions, "conditions did not match expected deleted ready condition, and overwritten issuing condition") + + t.Log("bob can re-add a Ready condition and not change Issuing condition") + crt = &cmapi.Certificate{ + TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, + Status: cmapi.CertificateStatus{ + Conditions: []cmapi.CertificateCondition{{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionFalse, Reason: "reason", Message: "message"}}, + }, + } + crtData, err = json.Marshal(crt) + assert.NoError(t, err) + _, err = bobCMClient.CertmanagerV1().Certificates(namespace).Patch( + ctx, name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: bobFieldManager}, "status", + ) + assert.NoError(t, err) + + crt, err = bobCMClient.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) + assert.NoError(t, err) + assert.Equal(t, []cmapi.CertificateCondition{ + {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, + {Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionFalse, Reason: "reason", Message: "message"}, + }, crt.Status.Conditions, "expected bob to be able to add a distinct ready condition after no longer owning the issuing condition") +} diff --git a/test/integration/certificates/issuing_controller_test.go b/test/integration/certificates/issuing_controller_test.go index b57f4560a..2fe31a129 100644 --- a/test/integration/certificates/issuing_controller_test.go +++ b/test/integration/certificates/issuing_controller_test.go @@ -64,7 +64,9 @@ func TestIssuingController(t *testing.T) { EnableOwnerRef: true, } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, "cert-manager-issuing-test", cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, controllerOptions) + ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, + cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, + controllerOptions, "cert-manage-certificates-issuing-test") c := controllerpkg.NewController( ctx, "issuing_test", @@ -197,8 +199,8 @@ func TestIssuingController(t *testing.T) { t.Fatal(err) } - // Wait for the Certificate to have the 'Issuing' condition removed, and for - // the signed certificate, ca, and private key stored in the Secret. + // Wait for the Certificate to have the 'Issuing' condition set to False, and + // for the signed certificate, ca, and private key stored in the Secret. err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { crt, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) if err != nil { @@ -206,8 +208,13 @@ func TestIssuingController(t *testing.T) { return false, nil } - if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing); cond != nil { - t.Logf("Certificate does not have expected condition, got=%#v", cond) + if !apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "Issued", + Message: "The certificate has been successfully issued", + }) { + t.Logf("Certificate does not have expected condition, got=%#v", apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing)) return false, nil } @@ -268,7 +275,9 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { EnableOwnerRef: true, } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, "cert-manager-issuing-test", cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, controllerOptions) + ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, + cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, + controllerOptions, "cert-manage-certificates-issuing-test") c := controllerpkg.NewController( ctx, "issuing_test", @@ -408,8 +417,8 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { t.Fatal(err) } - // Wait for the Certificate to have the 'Issuing' condition removed, and for - // the signed certificate, ca, and private key stored in the Secret. + // Wait for the Certificate to have the 'Issuing' condition set to False, and + // for the signed certificate, ca, and private key stored in the Secret. err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { crt, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) if err != nil { @@ -417,8 +426,13 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { return false, nil } - if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing); cond != nil { - t.Logf("Certificate does not have expected condition, got=%#v", cond) + if !apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "Issued", + Message: "The certificate has been successfully issued", + }) { + t.Logf("Certificate does not have expected condition, got=%#v", apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing)) return false, nil } @@ -481,7 +495,9 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { EnableOwnerRef: true, } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, "cert-manager-issuing-test", cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, controllerOptions) + ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, + cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, + controllerOptions, "cert-manage-certificates-issuing-test") c := controllerpkg.NewController( ctx, "issuing_test", @@ -614,8 +630,8 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { t.Fatal(err) } - // Wait for the Certificate to have the 'Issuing' condition removed, and for - // the signed certificate, ca, and private key stored in the Secret. + // Wait for the Certificate to have the 'Issuing' condition set to False, and + // for the signed certificate, ca, and private key stored in the Secret. err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { crt, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) if err != nil { @@ -623,8 +639,13 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { return false, nil } - if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing); cond != nil { - t.Logf("Certificate does not have expected condition, got=%#v", cond) + if !apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "Issued", + Message: "The certificate has been successfully issued", + }) { + t.Logf("Certificate does not have expected condition, got=%#v", apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing)) return false, nil } @@ -679,11 +700,13 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { } for k := range annotations { if _, ok := secret.Annotations[k]; ok { + t.Logf("annotations: %s", secret.Annotations) return false, nil } } for k := range labels { if _, ok := secret.Labels[k]; ok { + t.Logf("labels: %s", secret.Labels) return false, nil } } diff --git a/test/integration/certificates/trigger_controller_test.go b/test/integration/certificates/trigger_controller_test.go index e09879f7d..d25ebde51 100644 --- a/test/integration/certificates/trigger_controller_test.go +++ b/test/integration/certificates/trigger_controller_test.go @@ -66,7 +66,9 @@ func TestTriggerController(t *testing.T) { t.Fatal(err) } shouldReissue := policies.NewTriggerPolicyChain(fakeClock).Evaluate - ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, cmFactory, framework.NewEventRecorder(t), fakeClock, shouldReissue) + ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, + cmFactory, framework.NewEventRecorder(t), fakeClock, shouldReissue, + "cert-manage-certificates-trigger-test") c := controllerpkg.NewController( ctx, "trigger_test", @@ -178,7 +180,9 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { } // Start the trigger controller - ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, cmFactory, framework.NewEventRecorder(t), fakeClock, shoudReissue) + ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, + cmFactory, framework.NewEventRecorder(t), fakeClock, shoudReissue, + "cert-manage-certificates-trigger-test") c := controllerpkg.NewController( logf.NewContext(ctx, logf.Log, "trigger_controller_RenewNearExpiry"), "trigger_test",