diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index fffdca4..9d1fafb 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,8 +1,8 @@ ack_generate_info: - build_date: "2025-12-02T21:07:23Z" - build_hash: 06bffb95177cf873ee1b1a1c6f93cb30380c1e36 + build_date: "2025-12-12T00:10:27Z" + build_hash: 5c8b9050006ef6c7d3a97c279e7b1bc163f20a0a go_version: go1.25.1 - version: v0.56.0-2-g06bffb9 + version: v0.56.0-3-g5c8b905 api_directory_checksum: 5dc0b682f154f3479809e330d2760ff9575e9bea api_version: v1alpha1 aws_sdk_go_version: v1.32.6 diff --git a/config/crd/common/bases/services.k8s.aws_iamroleselectors.yaml b/config/crd/common/bases/services.k8s.aws_iamroleselectors.yaml index 9477c90..803a75c 100644 --- a/config/crd/common/bases/services.k8s.aws_iamroleselectors.yaml +++ b/config/crd/common/bases/services.k8s.aws_iamroleselectors.yaml @@ -63,6 +63,16 @@ spec: required: - names type: object + resourceLabelSelector: + description: LabelSelector is a label query over a set of resources. + properties: + matchLabels: + additionalProperties: + type: string + type: object + required: + - matchLabels + type: object resourceTypeSelector: items: properties: diff --git a/config/crd/common/kustomization.yaml b/config/crd/common/kustomization.yaml index 8165534..65cb01b 100644 --- a/config/crd/common/kustomization.yaml +++ b/config/crd/common/kustomization.yaml @@ -3,5 +3,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - - bases/services.k8s.aws_iamroleselectors.yaml - bases/services.k8s.aws_fieldexports.yaml + - bases/services.k8s.aws_iamroleselectors.yaml diff --git a/helm/crds/services.k8s.aws_iamroleselectors.yaml b/helm/crds/services.k8s.aws_iamroleselectors.yaml index 9477c90..803a75c 100644 --- a/helm/crds/services.k8s.aws_iamroleselectors.yaml +++ b/helm/crds/services.k8s.aws_iamroleselectors.yaml @@ -63,6 +63,16 @@ spec: required: - names type: object + resourceLabelSelector: + description: LabelSelector is a label query over a set of resources. + properties: + matchLabels: + additionalProperties: + type: string + type: object + required: + - matchLabels + type: object resourceTypeSelector: items: properties: diff --git a/pkg/resource/certificate/delta.go b/pkg/resource/certificate/delta.go index dcd7f2d..1c9b923 100644 --- a/pkg/resource/certificate/delta.go +++ b/pkg/resource/certificate/delta.go @@ -43,6 +43,7 @@ func newResourceDelta( return delta } compareCertificateIssuedAt(delta, a, b) + compareKeyAlgorithm(delta, a, b) if ackcompare.HasNilDifference(a.ko.Spec.CertificateARN, b.ko.Spec.CertificateARN) { delta.Add("Spec.CertificateARN", a.ko.Spec.CertificateARN, b.ko.Spec.CertificateARN) diff --git a/pkg/resource/certificate/hooks.go b/pkg/resource/certificate/hooks.go index 5311e0c..874949b 100644 --- a/pkg/resource/certificate/hooks.go +++ b/pkg/resource/certificate/hooks.go @@ -252,6 +252,28 @@ func DecryptPrivateKey(encryptedPEM, passphrase []byte, keyAlgorithm string) ([] } } +// normalizeKeyAlgorithm normalizes a KeyAlgorithm value by replacing all dash +// characters with underscore characters. This ensures consistency between the +// user-specified format (e.g., RSA_2048) and the AWS API response format +// (e.g., RSA-2048). +func normalizeKeyAlgorithm(algorithm string) string { + return strings.ReplaceAll(algorithm, "-", "_") +} + +func compareKeyAlgorithm( + delta *ackcompare.Delta, + a *resource, + b *resource, +) { + if a.ko.Spec.KeyAlgorithm != nil && b.ko.Spec.KeyAlgorithm != nil { + normalizedA := normalizeKeyAlgorithm(*a.ko.Spec.KeyAlgorithm) + normalizedB := normalizeKeyAlgorithm(*b.ko.Spec.KeyAlgorithm) + if normalizedA != normalizedB { + delta.Add("Spec.KeyAlgorithm", a.ko.Spec.KeyAlgorithm, b.ko.Spec.KeyAlgorithm) + } + } +} + func compareCertificateIssuedAt( delta *ackcompare.Delta, a *resource, diff --git a/templates/hooks/certificate/delta_pre_compare.go.tpl b/templates/hooks/certificate/delta_pre_compare.go.tpl index ae8da90..f9e6391 100644 --- a/templates/hooks/certificate/delta_pre_compare.go.tpl +++ b/templates/hooks/certificate/delta_pre_compare.go.tpl @@ -1 +1,2 @@ -compareCertificateIssuedAt(delta, a, b) \ No newline at end of file +compareCertificateIssuedAt(delta, a, b) +compareKeyAlgorithm(delta, a, b) \ No newline at end of file diff --git a/templates/hooks/certificate/sdk_read_one_pre_set_output.go.tpl b/templates/hooks/certificate/sdk_read_one_pre_set_output.go.tpl index b61ff49..5020f65 100644 --- a/templates/hooks/certificate/sdk_read_one_pre_set_output.go.tpl +++ b/templates/hooks/certificate/sdk_read_one_pre_set_output.go.tpl @@ -36,8 +36,8 @@ ko.Status.DomainValidations = nil } ko.Spec.Tags, err = listTags( - ctx, rm.sdkapi, rm.metrics, - string(*r.ko.Status.ACKResourceMetadata.ARN), + ctx, rm.sdkapi, rm.metrics, + string(*r.ko.Status.ACKResourceMetadata.ARN), ) if err != nil { return nil, err diff --git a/test/e2e/resources/certificate_with_key_algorithm.yaml b/test/e2e/resources/certificate_with_key_algorithm.yaml new file mode 100644 index 0000000..1dedf1c --- /dev/null +++ b/test/e2e/resources/certificate_with_key_algorithm.yaml @@ -0,0 +1,10 @@ +apiVersion: acm.services.k8s.aws/v1alpha1 +kind: Certificate +metadata: + name: $CERTIFICATE_NAME +spec: + domainName: $DOMAIN_NAME + keyAlgorithm: RSA_2048 + tags: + - key: environment + value: dev diff --git a/test/e2e/tests/test_certificate.py b/test/e2e/tests/test_certificate.py index 75df63d..20e04a2 100644 --- a/test/e2e/tests/test_certificate.py +++ b/test/e2e/tests/test_certificate.py @@ -241,6 +241,44 @@ def test_invalid( 'type': condition.CONDITION_TYPE_TERMINAL, } + @pytest.mark.parametrize('certificate_public', ['certificate_with_key_algorithm'], indirect=True) + def test_key_algorithm_normalization( + self, + certificate_public, + ): + """Test that KeyAlgorithm with underscores is preserved after sync. + + This test verifies that when a user specifies keyAlgorithm as RSA_2048 + (with underscores), the controller normalizes the AWS API response + (which uses dashes like RSA-2048) back to underscores, preventing + infinite reconciliation loops. + """ + (ref, cr) = certificate_public + assert "status" in cr + assert "ackResourceMetadata" in cr["status"] + assert "arn" in cr["status"]["ackResourceMetadata"] + certificate_arn = cr["status"]["ackResourceMetadata"]["arn"] + + # Wait for the resource to get synced + assert k8s.wait_on_condition( + ref, + "ACK.ResourceSynced", + "True", + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + + # Verify the keyAlgorithm field maintains underscore format after sync + cr = k8s.get_resource(ref) + assert "spec" in cr + assert "keyAlgorithm" in cr["spec"] + # The keyAlgorithm should remain RSA_2048 (with underscores), not RSA-2048 + assert cr["spec"]["keyAlgorithm"] == "RSA_2048", \ + f"Expected keyAlgorithm to be 'RSA_2048' but got '{cr['spec']['keyAlgorithm']}'" + + k8s.delete_custom_resource(ref) + time.sleep(DELETE_WAIT_AFTER_SECONDS) + certificate.wait_until_deleted(certificate_arn) + def test_import_certificate( self, certificate_import,