Skip to content

Commit e92f571

Browse files
arun717cursoragent
andauthored
CM-1043: Add IstioCSR e2e coverage and IstioCSR-ServiceMesh smoke tests (#427)
* e2e: add IstioCSR P0 coverage and OpenShift Service Mesh smoke tests * undo the label added for test case. * Fixing a failing test ISTIOCSR-P0-003 * Adding changes to fix ci failures. * e2e: drop external LetsEncrypt URL from IstioCSR P0-004 ACME test * e2e: fix label-filter quoting, HTTP-01 on OpenShift, and local-run hooks * Addressing coderabbit comments * Fixing filter in makefile causing CI issue. * Reverting all Makefile changes. * e2e: run vault JWT config via execVaultInPod instead of shell strings * e2e: add OSSM v3 Service Mesh smoke tests for IstioCSR Add Feature:ServiceMesh coverage that installs OSSM v3 when enabled, validates root CA distribution, mesh workload gRPC signing, workload CertificateRequests, and cross-namespace envoy traffic. Co-authored-by: Cursor <cursoragent@cursor.com> * e2e: avoid istio-system namespace collision in IstioCSR grpc tests Use a generated test namespace so BeforeEach setup does not time out when OSSM smoke tests already created the real istio-system namespace. Co-authored-by: Cursor <cursoragent@cursor.com> * e2e: isolate ServiceMesh operand from standalone IstioCSR grpc tests The operator accepts only one IstioCSR per cluster. OSSM smoke left an operand in istio-csr that blocked cert-manager-istio-csr deployment in istio-csr-e2e-* namespaces. Exclude Feature:ServiceMesh from the default CI label filter, clean up the OSSM operand after smoke tests, and add a clear timeout hint when a second instance is rejected. Co-authored-by: Cursor <cursoragent@cursor.com> * e2e: align IstioCSR istio namespace with generated test namespaces After moving grpc tests into istio-csr-e2e-* namespaces, the operand template still pointed issuer lookup at istio-system, so the operator never created cert-manager-istio-csr. Template the istio namespace, clean up leftover operands before the grpc suite, and improve timeout diagnostics. Co-authored-by: Cursor <cursoragent@cursor.com> * Addressed PR review comments. * e2e: harden grpcurl log parsing in IstioCSR tests * e2e: accept multi-line grpcurl JSON and add log excerpt on parse failure Extend parseGRPCurlLogEntry to unmarshal compact or multi-line JSON responses and include a truncated log excerpt in errors when parsing fails. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 514b381 commit e92f571

24 files changed

Lines changed: 2292 additions & 211 deletions

Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ TRUST_MANAGER_VERSION ?= v0.20.3
6363

6464
# --- Test Versions ---
6565

66+
# OpenShift Service Mesh versions for IstioCSR ServiceMesh e2e tests.
67+
# Keep servicemesh_helpers_test.go ossmDefault* constants in sync when bumping.
68+
E2E_OSM_ISTIO_VERSION ?= v1.24.3
69+
E2E_OSM_OPERATOR_VERSION ?= 3.2.5
70+
6671
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
6772
ENVTEST_K8S_VERSION ?= 1.32.0
6873

@@ -188,7 +193,7 @@ E2E_TIMEOUT ?= 2h
188193
# E2E_GINKGO_LABEL_FILTER is ginkgo label query for selecting tests.
189194
# See https://onsi.github.io/ginkgo/#spec-labels
190195
# The default is to run tests on the AWS platform.
191-
E2E_GINKGO_LABEL_FILTER ?= Platform: isSubsetOf {AWS,Generic} && CredentialsMode: isSubsetOf {Mint}
196+
E2E_GINKGO_LABEL_FILTER ?= Platform: isSubsetOf {AWS,Generic} && CredentialsMode: isSubsetOf {Mint} && !Feature:ServiceMesh
192197

193198
# ============================================================================
194199
# Default Target
@@ -279,6 +284,8 @@ test-apis: $(SETUP_ENVTEST) $(GINKGO)
279284
TEST ?=
280285
.PHONY: test-e2e
281286
test-e2e: test-e2e-wait-for-stable-state ## Run end-to-end tests.
287+
E2E_OSM_ISTIO_VERSION=$(E2E_OSM_ISTIO_VERSION) \
288+
E2E_OSM_OPERATOR_VERSION=$(E2E_OSM_OPERATOR_VERSION) \
282289
go test -C $(PROJECT_ROOT)/test/e2e \
283290
-timeout $(E2E_TIMEOUT) \
284291
-count 1 -v -p 1 \
@@ -590,4 +597,4 @@ $(OPERATOR_SDK): ## Download operator-sdk locally if necessary.
590597
hack/download-tools.sh operator-sdk $(OPERATOR_SDK)
591598

592599
$(OPM): ## Download opm locally if necessary.
593-
hack/download-tools.sh opm $(OPM)
600+
hack/download-tools.sh opm $(OPM)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
9+
certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
10+
certmanagerclientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned"
11+
apierrors "k8s.io/apimachinery/pkg/api/errors"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
)
14+
15+
func ensureClusterIssuer(ctx context.Context, client certmanagerclientset.Interface, issuer *certmanagerv1.ClusterIssuer) error {
16+
_, err := client.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{})
17+
if err == nil {
18+
return nil
19+
}
20+
if !apierrors.IsAlreadyExists(err) {
21+
return err
22+
}
23+
24+
existing, getErr := client.CertmanagerV1().ClusterIssuers().Get(ctx, issuer.Name, metav1.GetOptions{})
25+
if getErr != nil {
26+
return getErr
27+
}
28+
29+
issuer.ResourceVersion = existing.ResourceVersion
30+
_, err = client.CertmanagerV1().ClusterIssuers().Update(ctx, issuer, metav1.UpdateOptions{})
31+
return err
32+
}
33+
34+
func ensureIssuer(ctx context.Context, client certmanagerclientset.Interface, issuer *certmanagerv1.Issuer) error {
35+
_, err := client.CertmanagerV1().Issuers(issuer.Namespace).Create(ctx, issuer, metav1.CreateOptions{})
36+
if err == nil {
37+
return nil
38+
}
39+
if !apierrors.IsAlreadyExists(err) {
40+
return err
41+
}
42+
43+
existing, getErr := client.CertmanagerV1().Issuers(issuer.Namespace).Get(ctx, issuer.Name, metav1.GetOptions{})
44+
if getErr != nil {
45+
return getErr
46+
}
47+
48+
issuer.ResourceVersion = existing.ResourceVersion
49+
_, err = client.CertmanagerV1().Issuers(issuer.Namespace).Update(ctx, issuer, metav1.UpdateOptions{})
50+
return err
51+
}
52+
53+
func ensureCertificate(ctx context.Context, client certmanagerclientset.Interface, certificate *certmanagerv1.Certificate) error {
54+
_, err := client.CertmanagerV1().Certificates(certificate.Namespace).Create(ctx, certificate, metav1.CreateOptions{})
55+
if err == nil {
56+
return nil
57+
}
58+
if !apierrors.IsAlreadyExists(err) {
59+
return err
60+
}
61+
62+
existing, getErr := client.CertmanagerV1().Certificates(certificate.Namespace).Get(ctx, certificate.Name, metav1.GetOptions{})
63+
if getErr != nil {
64+
return getErr
65+
}
66+
67+
certificate.ResourceVersion = existing.ResourceVersion
68+
_, err = client.CertmanagerV1().Certificates(certificate.Namespace).Update(ctx, certificate, metav1.UpdateOptions{})
69+
return err
70+
}

test/e2e/config_template.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type IstioCSRGRPCurlJobConfig struct {
2727
IstioCSRStatus v1alpha1.IstioCSRStatus
2828
ClusterID string
2929
JobName string
30+
ProtoConfigMapName string
31+
ServiceAccountName string
3032
}
3133

3234
// ServiceMonitorConfig customizes fields in the ServiceMonitor spec
@@ -37,6 +39,46 @@ type ServiceMonitorConfig struct {
3739
ComponentName string
3840
}
3941

42+
// OSSMv3Config customizes OpenShift Service Mesh v3 install manifests.
43+
type OSSMv3Config struct {
44+
OperatorVersion string
45+
IstioVersion string
46+
ClusterID string
47+
CAAddress string
48+
}
49+
50+
const (
51+
istioCSRProfileMinimal = "minimal"
52+
istioCSRProfileOSSM = "ossm"
53+
istioCSROperandManifest = "testdata/istio/istio_csr_template.yaml"
54+
)
55+
56+
// IstioCSRConfig customizes the IstioCSR operand manifest.
57+
// Profile is "minimal" (default) for isolated IstioCSR tests or "ossm" for Service Mesh smoke.
58+
// IstioNamespace is spec.istioCSRConfig.istio.namespace; for the minimal profile it must match
59+
// the test namespace where the istio-ca Issuer is created.
60+
type IstioCSRConfig struct {
61+
Namespace string
62+
IstioNamespace string
63+
ClusterID string
64+
IstioDataPlaneNamespaceSelector string
65+
Profile string
66+
IssuerName string
67+
}
68+
69+
func istioCSRConfigForNS(namespace string, overrides IstioCSRConfig) IstioCSRConfig {
70+
if overrides.Namespace == "" {
71+
overrides.Namespace = namespace
72+
}
73+
if overrides.IstioNamespace == "" {
74+
overrides.IstioNamespace = namespace
75+
}
76+
if overrides.Profile == "" {
77+
overrides.Profile = istioCSRProfileMinimal
78+
}
79+
return overrides
80+
}
81+
4082
// replaceWithTemplate puts field values from a template struct
4183
func replaceWithTemplate(sourceFileContents string, templatedValues any) ([]byte, error) {
4284
tmpl, err := template.New("template").Option("missingkey=error").Parse(sourceFileContents)

test/e2e/issuer_acme_dns01_test.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ var _ = Describe("ACME Issuer DNS01 solver", Ordered, func() {
108108
},
109109
},
110110
}
111-
_, err = loader.KubeClient.CoreV1().ConfigMaps("cert-manager").Create(ctx, trustedCA, metav1.CreateOptions{})
112-
Expect(err).NotTo(HaveOccurred())
111+
Expect(library.UpsertConfigMap(ctx, loader.KubeClient, trustedCA)).NotTo(HaveOccurred())
113112

114113
DeferCleanup(func(cleanupCtx context.Context) {
115114
loader.KubeClient.CoreV1().ConfigMaps("cert-manager").Delete(cleanupCtx, "trusted-ca", metav1.DeleteOptions{})
@@ -272,8 +271,7 @@ var _ = Describe("ACME Issuer DNS01 solver", Ordered, func() {
272271
secretKey: secretAccessKey,
273272
},
274273
}
275-
_, err := loader.KubeClient.CoreV1().Secrets(namespace).Create(ctx, awsSecret, metav1.CreateOptions{})
276-
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to create secret %s", secretName))
274+
Expect(library.UpsertSecret(ctx, loader.KubeClient, awsSecret)).NotTo(HaveOccurred(), fmt.Sprintf("failed to create secret %s", secretName))
277275
}
278276

279277
// setupAmbientAWSCredentials sets up ambient AWS credentials via CredentialsRequest and subscription patch
@@ -343,8 +341,7 @@ var _ = Describe("ACME Issuer DNS01 solver", Ordered, func() {
343341
secretKey: serviceAccount,
344342
},
345343
}
346-
_, err := loader.KubeClient.CoreV1().Secrets(namespace).Create(ctx, gcpSecret, metav1.CreateOptions{})
347-
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to create secret %s", secretName))
344+
Expect(library.UpsertSecret(ctx, loader.KubeClient, gcpSecret)).NotTo(HaveOccurred(), fmt.Sprintf("failed to create secret %s", secretName))
348345
}
349346

350347
// setupAmbientGCPCredentials sets up ambient GCP credentials via CredentialsRequest and subscription patch
@@ -532,8 +529,7 @@ var _ = Describe("ACME Issuer DNS01 solver", Ordered, func() {
532529
secretKey: clientSecret,
533530
},
534531
}
535-
_, err := loader.KubeClient.CoreV1().Secrets(namespace).Create(ctx, azureSecret, metav1.CreateOptions{})
536-
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to create secret %s", secretName))
532+
Expect(library.UpsertSecret(ctx, loader.KubeClient, azureSecret)).NotTo(HaveOccurred(), fmt.Sprintf("failed to create secret %s", secretName))
537533
}
538534

539535
Context("with AWS Route53", Label("Platform:AWS", "CredentialsMode:Mint"), func() {
@@ -978,8 +974,7 @@ var _ = Describe("ACME Issuer DNS01 solver", Ordered, func() {
978974
"credentials": credContent,
979975
},
980976
}
981-
_, err := loader.KubeClient.CoreV1().Secrets("cert-manager").Create(ctx, stsSecret, metav1.CreateOptions{})
982-
Expect(err).NotTo(HaveOccurred(), "failed to create STS credential secret")
977+
Expect(library.UpsertSecret(ctx, loader.KubeClient, stsSecret)).NotTo(HaveOccurred(), "failed to create STS credential secret")
983978

984979
DeferCleanup(func(ctx context.Context) {
985980
By("Deleting manually created STS credential secret")
@@ -990,7 +985,7 @@ var _ = Describe("ACME Issuer DNS01 solver", Ordered, func() {
990985
})
991986

992987
By("patching subscription to inject 'CLOUD_CREDENTIALS_SECRET_NAME' env var")
993-
err = patchSubscriptionWithEnvVars(ctx, loader, map[string]string{
988+
err := patchSubscriptionWithEnvVars(ctx, loader, map[string]string{
994989
"CLOUD_CREDENTIALS_SECRET_NAME": secretName,
995990
})
996991
Expect(err).NotTo(HaveOccurred(), "failed to patch subscription with 'CLOUD_CREDENTIALS_SECRET_NAME'")
@@ -1243,8 +1238,7 @@ var _ = Describe("ACME Issuer DNS01 solver", Ordered, func() {
12431238
"service_account.json": credContent,
12441239
},
12451240
}
1246-
_, err = loader.KubeClient.CoreV1().Secrets("cert-manager").Create(ctx, stsSecret, metav1.CreateOptions{})
1247-
Expect(err).NotTo(HaveOccurred(), "failed to create GCP STS credentials secret")
1241+
Expect(library.UpsertSecret(ctx, loader.KubeClient, stsSecret)).NotTo(HaveOccurred(), "failed to create GCP STS credentials secret")
12481242

12491243
DeferCleanup(func(ctx context.Context, namespace, name string) {
12501244
By("Deleting GCP STS credentials secret")

test/e2e/issuer_acme_http01_test.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ import (
2727
. "github.com/onsi/gomega"
2828
)
2929

30+
// acmeHTTP01OpenShiftIngressClass is required on OpenShift so HTTP-01 challenge Ingresses get Routes.
31+
const acmeHTTP01OpenShiftIngressClass = "openshift-default"
32+
33+
func acmeHTTP01OpenShiftIngress() *acmev1.ACMEChallengeSolverHTTP01Ingress {
34+
ingressClass := acmeHTTP01OpenShiftIngressClass
35+
return &acmev1.ACMEChallengeSolverHTTP01Ingress{
36+
IngressClassName: &ingressClass,
37+
}
38+
}
39+
3040
var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered, func() {
3141
var ctx context.Context
3242
var cancel context.CancelFunc
@@ -68,8 +78,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
6878
},
6979
},
7080
}
71-
_, err = loader.KubeClient.CoreV1().ConfigMaps("cert-manager").Create(ctx, trustedCA, metav1.CreateOptions{})
72-
Expect(err).NotTo(HaveOccurred())
81+
Expect(library.UpsertConfigMap(ctx, loader.KubeClient, trustedCA)).NotTo(HaveOccurred())
7382

7483
DeferCleanup(func(cleanupCtx context.Context) {
7584
loader.KubeClient.CoreV1().ConfigMaps("cert-manager").Delete(cleanupCtx, "trusted-ca", metav1.DeleteOptions{})
@@ -126,7 +135,6 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
126135

127136
BeforeEach(func() {
128137
clusterIssuerName := "letsencrypt-http01"
129-
ingressClassName := "openshift-default"
130138
secretName = "ingress-http01-secret"
131139

132140
By("creating a cluster issuer")
@@ -146,9 +154,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
146154
Solvers: []acmev1.ACMEChallengeSolver{
147155
{
148156
HTTP01: &acmev1.ACMEChallengeSolverHTTP01{
149-
Ingress: &acmev1.ACMEChallengeSolverHTTP01Ingress{
150-
IngressClassName: &ingressClassName,
151-
},
157+
Ingress: acmeHTTP01OpenShiftIngress(),
152158
},
153159
},
154160
},
@@ -172,6 +178,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
172178
By("creating Ingress object")
173179
ingressHost = fmt.Sprintf("ahi-%s.%s", randomStr(3), appsDomain) // acronym for "ACME http-01 Ingress"
174180
pathType := networkingv1.PathTypePrefix
181+
ingressClassName := acmeHTTP01OpenShiftIngressClass
175182
ingress := &networkingv1.Ingress{
176183
ObjectMeta: metav1.ObjectMeta{
177184
Name: "ingress-http01",
@@ -311,7 +318,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
311318
Solvers: []acmev1.ACMEChallengeSolver{
312319
{
313320
HTTP01: &acmev1.ACMEChallengeSolverHTTP01{
314-
Ingress: &acmev1.ACMEChallengeSolverHTTP01Ingress{},
321+
Ingress: acmeHTTP01OpenShiftIngress(),
315322
},
316323
},
317324
},
@@ -369,8 +376,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
369376
"client-secret": "dummy-client-secret",
370377
},
371378
}
372-
_, err := loader.KubeClient.CoreV1().Secrets("cert-manager").Create(ctx, azureDNSSecret, metav1.CreateOptions{})
373-
Expect(err).NotTo(HaveOccurred(), "failed to create Azure DNS secret")
379+
Expect(library.UpsertSecret(ctx, loader.KubeClient, azureDNSSecret)).NotTo(HaveOccurred(), "failed to create Azure DNS secret")
374380

375381
DeferCleanup(func(ctx context.Context) {
376382
err := loader.KubeClient.CoreV1().Secrets("cert-manager").Delete(ctx, azureDNSSecretName, metav1.DeleteOptions{})
@@ -389,8 +395,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
389395
"secret-access-key": "dummy-secret-key",
390396
},
391397
}
392-
_, err = loader.KubeClient.CoreV1().Secrets("cert-manager").Create(ctx, route53Secret, metav1.CreateOptions{})
393-
Expect(err).NotTo(HaveOccurred(), "failed to create Route53 secret")
398+
Expect(library.UpsertSecret(ctx, loader.KubeClient, route53Secret)).NotTo(HaveOccurred(), "failed to create Route53 secret")
394399

395400
DeferCleanup(func(ctx context.Context) {
396401
err := loader.KubeClient.CoreV1().Secrets("cert-manager").Delete(ctx, route53SecretName, metav1.DeleteOptions{})
@@ -423,7 +428,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
423428
DNSZones: []string{testDomain},
424429
},
425430
HTTP01: &acmev1.ACMEChallengeSolverHTTP01{
426-
Ingress: &acmev1.ACMEChallengeSolverHTTP01Ingress{},
431+
Ingress: acmeHTTP01OpenShiftIngress(),
427432
},
428433
},
429434
// Solver 2: DNS-01 (Azure) with specific dnsNames selector
@@ -470,7 +475,7 @@ var _ = Describe("ACME Issuer HTTP01 solver", Label("Platform:Generic"), Ordered
470475
},
471476
},
472477
}
473-
_, err = certmanagerClient.CertmanagerV1().ClusterIssuers().Create(ctx, clusterIssuer, metav1.CreateOptions{})
478+
_, err := certmanagerClient.CertmanagerV1().ClusterIssuers().Create(ctx, clusterIssuer, metav1.CreateOptions{})
474479
Expect(err).NotTo(HaveOccurred(), "failed to create ClusterIssuer")
475480

476481
DeferCleanup(func(ctx context.Context) {

0 commit comments

Comments
 (0)