Skip to content

Commit f0996cf

Browse files
Merge pull request #401 from openshift-cherrypick-robot/cherry-pick-385-to-cert-manager-1.19
[cert-manager-1.19] CM-902: Add table-driven unit tests and improve coverage
2 parents cc555bb + f8b7a78 commit f0996cf

10 files changed

Lines changed: 1982 additions & 51 deletions

File tree

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package certmanager
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
appsv1 "k8s.io/api/apps/v1"
8+
corev1 "k8s.io/api/core/v1"
9+
apierrors "k8s.io/apimachinery/pkg/api/errors"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/client-go/informers"
12+
"k8s.io/client-go/kubernetes/fake"
13+
"k8s.io/client-go/tools/cache"
14+
15+
configv1 "github.com/openshift/api/config/v1"
16+
configinformersv1 "github.com/openshift/client-go/config/informers/externalversions/config/v1"
17+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
18+
19+
"github.com/openshift/cert-manager-operator/pkg/operator/operatorclient"
20+
)
21+
22+
const testSecretName = "cloud-creds"
23+
24+
// The following helpers return the full err.Error() the hook produces for each failure mode
25+
// (same NotFound GroupResource/name and fmt.Errorf wording as withCloudCredentials; test-only).
26+
func expectedCloudSecretRetryingNotFound(secretName string) string {
27+
nf := apierrors.NewNotFound(corev1.Resource("secret"), secretName)
28+
return fmt.Errorf("(Retrying) cloud secret %q doesn't exist due to %w", secretName, nf).Error()
29+
}
30+
31+
func expectedUnsupportedCloudProvider(platform configv1.PlatformType) string {
32+
return fmt.Errorf("unsupported cloud provider %q for mounting cloud credentials secret", platform).Error()
33+
}
34+
35+
func expectedInfrastructureClusterNotFound() string {
36+
return apierrors.NewNotFound(configv1.Resource("infrastructure"), "cluster").Error()
37+
}
38+
39+
func TestWithCloudCredentials(t *testing.T) {
40+
tests := []struct {
41+
name string
42+
deploymentName string
43+
secretName string
44+
secretInStore bool
45+
decoySecretOnly bool // lister has a different secret name so Get(secretName) fails (not brittle on tt.name)
46+
platformType configv1.PlatformType
47+
wantErr string // full hook err.Error(); empty => expect nil error
48+
wantVolumes int
49+
wantMountPath string
50+
wantAWSEnv bool
51+
noInfra bool // if true, infra indexer is left empty so Get("cluster") fails
52+
}{
53+
{
54+
name: "non-controller deployment no-op",
55+
deploymentName: certmanagerWebhookDeployment,
56+
secretName: testSecretName,
57+
platformType: configv1.AWSPlatformType,
58+
wantVolumes: 0,
59+
},
60+
{
61+
name: "empty secret name returns nil",
62+
deploymentName: certmanagerControllerDeployment,
63+
secretName: "",
64+
platformType: configv1.AWSPlatformType,
65+
wantVolumes: 0,
66+
},
67+
{
68+
name: "secret not found returns retry error",
69+
deploymentName: certmanagerControllerDeployment,
70+
secretName: "missing-secret",
71+
secretInStore: false,
72+
decoySecretOnly: true,
73+
platformType: configv1.AWSPlatformType,
74+
wantErr: expectedCloudSecretRetryingNotFound("missing-secret"),
75+
wantVolumes: 0,
76+
},
77+
{
78+
name: "AWS adds volume, mount and env",
79+
deploymentName: certmanagerControllerDeployment,
80+
secretName: testSecretName,
81+
secretInStore: true,
82+
platformType: configv1.AWSPlatformType,
83+
wantVolumes: 1,
84+
wantMountPath: awsCredentialsDir,
85+
wantAWSEnv: true,
86+
},
87+
{
88+
name: "GCP adds volume and mount, no AWS env",
89+
deploymentName: certmanagerControllerDeployment,
90+
secretName: testSecretName,
91+
secretInStore: true,
92+
platformType: configv1.GCPPlatformType,
93+
wantVolumes: 1,
94+
wantMountPath: gcpCredentialsDir,
95+
wantAWSEnv: false,
96+
},
97+
{
98+
name: "unsupported platform returns error",
99+
deploymentName: certmanagerControllerDeployment,
100+
secretName: testSecretName,
101+
secretInStore: true,
102+
platformType: configv1.PlatformType("Unsupported"),
103+
wantErr: expectedUnsupportedCloudProvider(configv1.PlatformType("Unsupported")),
104+
wantVolumes: 0,
105+
},
106+
{
107+
name: "infra not found returns error",
108+
deploymentName: certmanagerControllerDeployment,
109+
secretName: testSecretName,
110+
secretInStore: true,
111+
platformType: configv1.AWSPlatformType,
112+
wantErr: expectedInfrastructureClusterNotFound(),
113+
noInfra: true,
114+
wantVolumes: 0,
115+
},
116+
{
117+
name: "Azure platform is unsupported",
118+
deploymentName: certmanagerControllerDeployment,
119+
secretName: testSecretName,
120+
secretInStore: true,
121+
platformType: configv1.AzurePlatformType,
122+
wantErr: expectedUnsupportedCloudProvider(configv1.AzurePlatformType),
123+
wantVolumes: 0,
124+
},
125+
}
126+
for _, tt := range tests {
127+
t.Run(tt.name, func(t *testing.T) {
128+
var kubeClient *fake.Clientset
129+
if tt.secretInStore {
130+
secret := &corev1.Secret{
131+
ObjectMeta: metav1.ObjectMeta{Name: tt.secretName, Namespace: operatorclient.TargetNamespace},
132+
}
133+
kubeClient = fake.NewSimpleClientset(secret)
134+
} else if tt.decoySecretOnly {
135+
kubeClient = fake.NewSimpleClientset(&corev1.Secret{
136+
ObjectMeta: metav1.ObjectMeta{Name: "other", Namespace: operatorclient.TargetNamespace},
137+
})
138+
} else {
139+
kubeClient = fake.NewSimpleClientset()
140+
}
141+
kubeInformers := informers.NewSharedInformerFactory(kubeClient, 0)
142+
if tt.secretInStore || tt.wantErr != "" {
143+
secret := &corev1.Secret{
144+
ObjectMeta: metav1.ObjectMeta{Name: tt.secretName, Namespace: operatorclient.TargetNamespace},
145+
}
146+
if tt.decoySecretOnly {
147+
secret.Name = "other"
148+
}
149+
kubeInformers.Core().V1().Secrets().Informer().GetStore().Add(secret)
150+
}
151+
stopCh := make(chan struct{})
152+
defer close(stopCh)
153+
kubeInformers.Start(stopCh)
154+
if tt.secretInStore || tt.wantErr != "" {
155+
if !cache.WaitForCacheSync(stopCh, kubeInformers.Core().V1().Secrets().Informer().HasSynced) {
156+
t.Fatal("secret informer did not sync")
157+
}
158+
}
159+
160+
infraIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
161+
if !tt.noInfra {
162+
infra := &configv1.Infrastructure{
163+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
164+
Status: configv1.InfrastructureStatus{
165+
PlatformStatus: &configv1.PlatformStatus{Type: tt.platformType},
166+
},
167+
}
168+
_ = infraIndexer.Add(infra)
169+
}
170+
infraInformer := &fakeInfrastructureInformer{lister: configlistersv1.NewInfrastructureLister(infraIndexer)}
171+
172+
hook := withCloudCredentials(
173+
kubeInformers.Core().V1().Secrets(),
174+
infraInformer,
175+
tt.deploymentName,
176+
tt.secretName,
177+
)
178+
deployment := &appsv1.Deployment{
179+
Spec: appsv1.DeploymentSpec{
180+
Template: corev1.PodTemplateSpec{
181+
Spec: corev1.PodSpec{
182+
Containers: []corev1.Container{{Name: "controller"}},
183+
},
184+
},
185+
},
186+
}
187+
err := hook(nil, deployment)
188+
189+
if tt.wantErr != "" {
190+
if err == nil {
191+
t.Fatalf("expected error %q, got nil", tt.wantErr)
192+
}
193+
if got := err.Error(); got != tt.wantErr {
194+
t.Errorf("error = %q, want %q", got, tt.wantErr)
195+
}
196+
return
197+
}
198+
if err != nil {
199+
t.Fatalf("unexpected error: %v", err)
200+
}
201+
if n := len(deployment.Spec.Template.Spec.Volumes); n != tt.wantVolumes {
202+
t.Errorf("volumes count = %d, want %d", n, tt.wantVolumes)
203+
}
204+
if tt.wantVolumes > 0 {
205+
if deployment.Spec.Template.Spec.Volumes[0].Name != cloudCredentialsVolumeName {
206+
t.Errorf("volume name = %q, want %q", deployment.Spec.Template.Spec.Volumes[0].Name, cloudCredentialsVolumeName)
207+
}
208+
if tt.wantMountPath != "" {
209+
mounts := deployment.Spec.Template.Spec.Containers[0].VolumeMounts
210+
if len(mounts) == 0 {
211+
t.Fatalf("expected VolumeMount for mount path %q, got none (containers[0].VolumeMounts is empty)", tt.wantMountPath)
212+
}
213+
if mounts[0].MountPath != tt.wantMountPath {
214+
t.Errorf("mount path = %q, want %q", mounts[0].MountPath, tt.wantMountPath)
215+
}
216+
}
217+
var hasAWS bool
218+
for _, e := range deployment.Spec.Template.Spec.Containers[0].Env {
219+
if e.Name == "AWS_SDK_LOAD_CONFIG" {
220+
hasAWS = true
221+
break
222+
}
223+
}
224+
if hasAWS != tt.wantAWSEnv {
225+
t.Errorf("AWS_SDK_LOAD_CONFIG present = %v, want %v", hasAWS, tt.wantAWSEnv)
226+
}
227+
}
228+
})
229+
}
230+
}
231+
232+
// fakeInfrastructureInformer implements configinformersv1.InfrastructureInformer using a fixed lister.
233+
type fakeInfrastructureInformer struct {
234+
lister configlistersv1.InfrastructureLister
235+
}
236+
237+
func (f *fakeInfrastructureInformer) Informer() cache.SharedIndexInformer {
238+
return nil
239+
}
240+
241+
func (f *fakeInfrastructureInformer) Lister() configlistersv1.InfrastructureLister {
242+
return f.lister
243+
}
244+
245+
var _ configinformersv1.InfrastructureInformer = (*fakeInfrastructureInformer)(nil)

0 commit comments

Comments
 (0)