diff --git a/src/k8s-configuration/HISTORY.rst b/src/k8s-configuration/HISTORY.rst index 381d14141ae..5a16d4e71a6 100644 --- a/src/k8s-configuration/HISTORY.rst +++ b/src/k8s-configuration/HISTORY.rst @@ -2,6 +2,10 @@ Release History =============== +2.3.0 +++++++++++++++++++ +* Added support for using OCIRepository as a source type in Flux configurations. + 2.2.0 ++++++++++++++++++ * Introduce a new feature to add provider authentication for git repositories. diff --git a/src/k8s-configuration/azext_k8s_configuration/_help.py b/src/k8s-configuration/azext_k8s_configuration/_help.py index d30d5f81ea5..63932e22f65 100644 --- a/src/k8s-configuration/azext_k8s_configuration/_help.py +++ b/src/k8s-configuration/azext_k8s_configuration/_help.py @@ -85,6 +85,13 @@ --kind azblob --url https://mystorageaccount.blob.core.windows.net \\ --container-name my-container --kustomization name=my-kustomization \\ --account-key my-account-key + - name: Create a Kubernetes v2 Flux Configuration with OCI Source Kind + text: |- + az k8s-configuration flux create --resource-group my-resource-group \\ + --cluster-name mycluster --cluster-type connectedClusters \\ + --name myconfig --scope cluster --namespace my-namespace \\ + --kind oci --url oci://ghcr.io/owner/repo/manifests/podinfo \\ + --kustomization name=my-kustomization --use-workload-identity """ helps[ @@ -109,6 +116,11 @@ az k8s-configuration flux update --resource-group my-resource-group \\ --cluster-name mycluster --cluster-type connectedClusters --name myconfig \\ --container-name other-container + - name: Update a Flux v2 Kubernetes configuration with OCI Source Kind to use connect insecurely + text: |- + az k8s-configuration flux update --resource-group my-resource-group \\ + --cluster-name mycluster --cluster-type connectedClusters --name myconfig \\ + --oci-insecure """ helps[ diff --git a/src/k8s-configuration/azext_k8s_configuration/_params.py b/src/k8s-configuration/azext_k8s_configuration/_params.py index df822dd9524..cbedd2018fc 100644 --- a/src/k8s-configuration/azext_k8s_configuration/_params.py +++ b/src/k8s-configuration/azext_k8s_configuration/_params.py @@ -19,6 +19,7 @@ from .action import ( KustomizationAddAction, + VerifyConfigAction ) from . import consts @@ -65,7 +66,7 @@ def load_arguments(self, _): ) c.argument( "kind", - arg_type=get_enum_type([consts.GIT, consts.BUCKET, consts.AZBLOB]), + arg_type=get_enum_type([consts.GIT, consts.BUCKET, consts.AZBLOB, consts.OCI]), help="Source kind to reconcile", ) c.argument( @@ -86,13 +87,13 @@ def load_arguments(self, _): ) c.argument( "tag", - arg_group="Git Repo Ref", - help="Tag within the git source to reconcile with the cluster", + arg_group="Git Repo Ref / OCI Repo Ref", + help="Tag within the git or OCI source to reconcile with the cluster", ) c.argument( "semver", - arg_group="Git Repo Ref", - help="Semver range within the git source to reconcile with the cluster", + arg_group="Git Repo Ref / OCI Repo Ref", + help="Semver range within the git or OCI source to reconcile with the cluster", ) c.argument( "commit", @@ -238,6 +239,76 @@ def load_arguments(self, _): options_list=["--mi-client-id", "--managed-identity-client-id"], help="The client ID of the managed identity for authentication with Azure Blob", ) + c.argument( + "digest", + arg_group="OCI Repo Ref", + help="Digest of the OCI artifact to reconcile with the cluster", + ) + c.argument( + "oci_media_type", + arg_group="OCI Repo Ref", + options_list=["--oci-media-type", "--oci-layer-selector-media-type"], + help="OCI artifact layer media type to select for extraction or copy.", + ) + c.argument( + "oci_operation", + arg_group="OCI Repo Ref", + arg_type=get_enum_type(["extract", "copy"]), + options_list=["--oci-operation", "--oci-layer-selector-operation"], + help="Operation to perform on the selected OCI artifact layer: 'extract' to extract the layer, 'copy' to copy the tarball as-is (default: extract)", + ) + c.argument( + "tls_ca_certificate", + arg_group="OCI Repository Auth", + help="Base64-encoded CA certificate for TLS communication with OCI repository", + ) + c.argument( + "tls_client_certificate", + arg_group="OCI Repository Auth", + help="Base64-encoded client certificate for TLS authentication with OCI repository", + ) + c.argument( + "tls_private_key", + arg_group="OCI Repository Auth", + help="Base64-encoded private key for TLS authentication with OCI repository", + ) + c.argument( + "service_account_name", + arg_group="OCI Repository Auth", + help="Name of the Kubernetes service account to use for accessing the OCI repository", + ) + c.argument( + "use_workload_identity", + arg_group="OCI Repository Auth", + arg_type=get_three_state_flag(), + help="Use workload identity for authentication with OCI repository", + ) + c.argument( + "oci_insecure", + arg_type=get_three_state_flag(), + help="Allow connecting to an insecure (HTTP) OCI container registry.", + ) + c.argument( + "verification_provider", + action=VerifyConfigAction, + arg_group="OCI Repository Auth", + help="Provider used for OCI verification." + ) + c.argument( + "match_oidc_identity", + action=VerifyConfigAction, + arg_group="OCI Repository Auth", + nargs="+", + help="List of OIDC identities to match for verification of OCI artifacts. Each entry should be a JSON string with 'issuer' and 'subject' fields." + ) + c.argument( + "verification_config", + action=VerifyConfigAction, + arg_group="OCI Repository Auth", + nargs="+", + help="An object containing trusted public keys of trusted authors for OCI artifacts." + ) + with self.argument_context("k8s-configuration flux update") as c: c.argument( diff --git a/src/k8s-configuration/azext_k8s_configuration/action.py b/src/k8s-configuration/azext_k8s_configuration/action.py index 5fce0fdd042..74ec56f9f93 100644 --- a/src/k8s-configuration/azext_k8s_configuration/action.py +++ b/src/k8s-configuration/azext_k8s_configuration/action.py @@ -5,6 +5,7 @@ # pylint: disable=protected-access import argparse +import json from azure.cli.core.azclierror import InvalidArgumentValueError from .vendored_sdks.v2024_04_01_preview.models import ( KustomizationDefinition, @@ -75,3 +76,41 @@ def __call__(self, parser, namespace, values, option_string=None): ), option_string, ) + + +class VerifyConfigAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + # Handle verification_provider (simple string) + if self.dest == "verification_provider": + setattr(namespace, self.dest, values) + return + + # Handle match_oidc_identity (list of JSON strings) + if self.dest == "match_oidc_identity": + identities = [] + for entry in values: + try: + obj = json.loads(entry) + if not isinstance(obj, dict) or "issuer" not in obj or "subject" not in obj: + raise ValueError() + identities.append({"issuer": obj["issuer"], "subject": obj["subject"]}) + except Exception: + raise InvalidArgumentValueError( + "Each entry for --match-oidc-identity must be a JSON string with 'issuer' and 'subject' fields." + ) + setattr(namespace, self.dest, identities) + return + + # Handle verification_config (list of key=value) + if self.dest == "verification_config": + config = {} + for item in values: + try: + key, value = item.split("=", 1) + config[key] = value + except Exception: + raise InvalidArgumentValueError( + "Each entry for --verification-config must be in key=value format." + ) + setattr(namespace, self.dest, config) + return \ No newline at end of file diff --git a/src/k8s-configuration/azext_k8s_configuration/consts.py b/src/k8s-configuration/azext_k8s_configuration/consts.py index bb22a1e649f..478a404e696 100644 --- a/src/k8s-configuration/azext_k8s_configuration/consts.py +++ b/src/k8s-configuration/azext_k8s_configuration/consts.py @@ -255,6 +255,28 @@ "mi_client_id", } +OCI_REPO_REQUIRED_PARAMS = {"url"} +OCI_REPO_VALID_PARAMS = { + "url", + "tag", + "semver", + "digest", + "sync_interval", + "timeout", + "local_auth_ref", + "oci_media_type", + "oci_operation", + "tls_ca_certificate", + "tls_client_certificate", + "tls_private_key", + "service_account_name", + "use_workload_identity", + "oci_insecure", + "verification_provider", + "match_oidc_identity", + "verification_config", +} + DEPENDENCY_KEYS = ["dependencies", "depends_on", "dependsOn", "depends"] SYNC_INTERVAL_KEYS = ["interval", "sync_interval", "syncInterval"] RETRY_INTERVAL_KEYS = ["retryInterval", "retry_interval"] @@ -266,6 +288,7 @@ VALID_GIT_URL_REGEX = r"^(((http|https|ssh)://)|(git@))" VALID_BUCKET_URL_REGEX = r"^(((http|https)://))" VALID_AZUREBLOB_URL_REGEX = r"^(((http|https)://))" +VALID_OCI_URL_REGEX = r"^oci://.*$" VALID_KUBERNETES_DNS_SUBDOMAIN_NAME_REGEX = r"^[a-z0-9]([\.\-a-z0-9]*[a-z0-9])?$" VALID_KUBERNETES_DNS_NAME_REGEX = r"^[a-z0-9]([\-a-z0-9]*[a-z0-9])?$" @@ -274,8 +297,10 @@ BUCKET = "bucket" BUCKET_CAPS = "Bucket" AZBLOB = "azblob" +OCI = "oci" AZURE_BLOB = "AzureBlob" GIT_REPOSITORY = "GitRepository" +OCI_REPOSITORY = "OCIRepository" CONNECTED_CLUSTER_TYPE = "connectedclusters" MANAGED_CLUSTER_TYPE = "managedclusters" diff --git a/src/k8s-configuration/azext_k8s_configuration/providers/FluxConfigurationProvider.py b/src/k8s-configuration/azext_k8s_configuration/providers/FluxConfigurationProvider.py index 08264b88114..d744448d3dd 100644 --- a/src/k8s-configuration/azext_k8s_configuration/providers/FluxConfigurationProvider.py +++ b/src/k8s-configuration/azext_k8s_configuration/providers/FluxConfigurationProvider.py @@ -45,6 +45,7 @@ validate_duration, validate_private_key, validate_url_with_params, + validate_oci_url, ) from .. import consts from ..vendored_sdks.v2024_11_01.models import ( @@ -62,6 +63,13 @@ KustomizationDefinition, KustomizationPatchDefinition, SourceKindType, + OCIRepositoryDefinition, + OCIRepositoryPatchDefinition, + OCIRepositoryRefDefinition, + MatchOidcIdentityDefinition, + LayerSelectorDefinition, + VerifyDefinition, + TlsConfigDefinition ) from ..vendored_sdks.v2022_07_01.models import Extension, Identity @@ -167,6 +175,18 @@ def create_config( sas_token=None, mi_client_id=None, cluster_resource_provider=None, + digest=None, + oci_media_type=None, + oci_operation=None, + tls_ca_certificate=None, + tls_client_certificate=None, + tls_private_key=None, + service_account_name=None, + use_workload_identity=None, + oci_insecure=None, + verification_provider=None, + match_oidc_identity=None, + verification_config=None, ): # Get Resource Provider to call @@ -206,6 +226,18 @@ def create_config( sp_client_secret=sp_client_secret, sp_client_cert_send_chain=sp_client_cert_send_chain, mi_client_id=mi_client_id, + digest=digest, + oci_media_type=oci_media_type, + oci_operation=oci_operation, + tls_ca_certificate=tls_ca_certificate, + tls_client_certificate=tls_client_certificate, + tls_private_key=tls_private_key, + service_account_name=service_account_name, + use_workload_identity=use_workload_identity, + oci_insecure=oci_insecure, + verification_provider=verification_provider, + match_oidc_identity=match_oidc_identity, + verification_config=verification_config, ) # This update func is a generated update function that modifies @@ -303,6 +335,18 @@ def update_config( sas_token=None, mi_client_id=None, cluster_resource_provider=None, + digest=None, + oci_media_type=None, + oci_operation=None, + tls_ca_certificate=None, + tls_client_certificate=None, + tls_private_key=None, + service_account_name=None, + use_workload_identity=None, + oci_insecure=None, + verification_provider=None, + match_oidc_identity=None, + verification_config=None, ): # Get Resource Provider to call @@ -347,6 +391,18 @@ def update_config( sp_client_secret=sp_client_secret, sp_client_cert_send_chain=sp_client_cert_send_chain, mi_client_id=mi_client_id, + digest=digest, + oci_media_type=oci_media_type, + oci_operation=oci_operation, + tls_ca_certificate=tls_ca_certificate, + tls_client_certificate=tls_client_certificate, + tls_private_key=tls_private_key, + service_account_name=service_account_name, + use_workload_identity=use_workload_identity, + oci_insecure=oci_insecure, + verification_provider=verification_provider, + match_oidc_identity=match_oidc_identity, + verification_config=verification_config, ) # This update func is a generated update function that modifies @@ -827,6 +883,8 @@ def source_kind_generator_factory(kind=consts.GIT, **kwargs): return GitRepositoryGenerator(**kwargs) if kind == consts.BUCKET: return BucketGenerator(**kwargs) + if kind == consts.OCI: + return OCIRepositoryGenerator(**kwargs) return AzureBlobGenerator(**kwargs) @@ -835,6 +893,8 @@ def convert_to_cli_source_kind(rp_source_kind): return consts.GIT elif rp_source_kind == consts.BUCKET_CAPS: return consts.BUCKET + elif rp_source_kind == consts.OCI_REPOSITORY: + return consts.OCI return consts.AZBLOB @@ -1196,6 +1256,160 @@ def azure_blob_patch_updater(config): return azure_blob_patch_updater +class OCIRepositoryGenerator(SourceKindGenerator): + def __init__(self, **kwargs): + # Common Pre-Validation + super().__init__( + consts.OCI, consts.OCI_REPO_REQUIRED_PARAMS, consts.OCI_REPO_VALID_PARAMS + ) + super().validate_params(**kwargs) + + # Pre-Validations + validate_duration("--timeout", kwargs.get("timeout")) + validate_duration("--sync-interval", kwargs.get("sync_interval")) + + self.kwargs = kwargs + self.url = kwargs.get("url") + self.timeout = kwargs.get("timeout") + self.sync_interval = kwargs.get("sync_interval") + self.local_auth_ref = kwargs.get("local_auth_ref") + self.service_account_name = kwargs.get("service_account_name") + self.use_workload_identity = kwargs.get("use_workload_identity") + self.oci_insecure = kwargs.get("oci_insecure") + + self.layer_selector = None + if any( + [ + kwargs.get("oci_media_type"), + kwargs.get("oci_operation") + ] + ): + self.layer_selector = LayerSelectorDefinition( + media_type=kwargs.get("oci_media_type"), + operation=kwargs.get("oci_operation"), + ) + + self.repository_ref = None + if any( + [ + kwargs.get("tag"), + kwargs.get("semver"), + kwargs.get("digest"), + ] + ): + self.repository_ref = OCIRepositoryRefDefinition( + tag=kwargs.get("tag"), + semver=kwargs.get("semver"), + digest=kwargs.get("digest"), + ) + + self.tls_config = None + if any( + [ + kwargs.get("tls_ca_certificate"), + kwargs.get("tls_client_certificate"), + kwargs.get("tls_private_key"), + ] + ): + self.tls_config = TlsConfigDefinition( + ca_certificate=kwargs.get("tls_ca_certificate"), + client_certificate=kwargs.get("tls_client_certificate"), + private_key=kwargs.get("tls_private_key"), + ) + + self.match_oidc_identities = None + if kwargs.get("match_oidc_identity"): + self.match_oidc_identities = [ + MatchOidcIdentityDefinition( + issuer=identity["issuer"], + subject=identity["subject"] + ) + for identity in kwargs.get("match_oidc_identity", []) + ] + + self.verification_config = None + if kwargs.get("verification_config"): + self.verification_config = { + key: value for key, value in kwargs.get("verification_config", {}).items() + } + + self.verify = None + if any( + [ + kwargs.get("verification_provider"), + self.match_oidc_identities, + self.verification_config, + ] + ): + self.verify = VerifyDefinition( + provider=kwargs.get("verification_provider"), + match_oidc_identity=self.match_oidc_identities, + verification_config=self.verification_config, + ) + + def validate(self): + super().validate_required_params(**self.kwargs) + validate_oci_url(self.url) + + def generate_update_func(self): + """ + generate_update_func(self) generates a function to add a OCIRepository + object to the flux configuration for the PUT case + """ + self.validate() + + def oci_repo_updater(config): + config.oci_repository = OCIRepositoryDefinition( + url=self.url, + timeout_in_seconds=parse_duration(self.timeout), + sync_interval_in_seconds=parse_duration(self.sync_interval), + local_auth_ref=self.local_auth_ref, + service_account_name=self.service_account_name, + use_workload_identity=self.use_workload_identity, + layer_selector=self.layer_selector, + repository_ref=self.repository_ref, + tls_config=self.tls_config, + verify=self.verify, + insecure=self.oci_insecure, + ) + config.source_kind = SourceKindType.OCI_REPOSITORY + return config + + return oci_repo_updater + + def generate_patch_update_func(self, swapped_kind): + """ + generate_patch_update_func(self) generates a function update the OCIRepository + object to the flux configuration for the PATCH case. + If the source kind has been changed, we also set the GitRepository, Bucket And AzureBlob to null + """ + + def oci_repo_patch_updater(config): + if any(kwarg is not None for kwarg in self.kwargs.values()): + config.oci_repository = OCIRepositoryPatchDefinition( + url=self.url, + timeout_in_seconds=parse_duration(self.timeout), + sync_interval_in_seconds=parse_duration(self.sync_interval), + local_auth_ref=self.local_auth_ref, + service_account_name=self.service_account_name, + use_workload_identity=self.use_workload_identity, + layer_selector=self.layer_selector, + repository_ref=self.repository_ref, + tls_config=self.tls_config, + verify=self.verify, + insecure=self.oci_insecure, + ) + if swapped_kind: + self.validate() + config.source_kind = SourceKindType.OCI_REPOSITORY + config.azure_blob = AzureBlobPatchDefinition() + config.bucket = BucketPatchDefinition() + config.git_repository = GitRepositoryPatchDefinition() + return config + + return oci_repo_patch_updater + + def get_protected_settings( ssh_private_key, ssh_private_key_file, https_key, bucket_secret_key ): diff --git a/src/k8s-configuration/azext_k8s_configuration/validators.py b/src/k8s-configuration/azext_k8s_configuration/validators.py index bed3b5de216..93c39390c7c 100644 --- a/src/k8s-configuration/azext_k8s_configuration/validators.py +++ b/src/k8s-configuration/azext_k8s_configuration/validators.py @@ -161,6 +161,12 @@ def validate_bucket_url(url: str): raise InvalidArgumentValueError( consts.INVALID_URL_ERROR, consts.INVALID_URL_HELP ) + +def validate_oci_url(url: str): + if not re.match(consts.VALID_OCI_URL_REGEX, url): + raise InvalidArgumentValueError( + consts.INVALID_URL_ERROR, consts.INVALID_URL_HELP + ) # Helper diff --git a/src/k8s-configuration/setup.py b/src/k8s-configuration/setup.py index 53e7255ca5d..0b104bda1b7 100644 --- a/src/k8s-configuration/setup.py +++ b/src/k8s-configuration/setup.py @@ -16,7 +16,7 @@ logger.warn("Wheel is not available, disabling bdist_wheel hook") -VERSION = "2.2.0" +VERSION = "2.3.0" # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers diff --git a/testing/Bootstrap.ps1 b/testing/Bootstrap.ps1 index 5e92a9304e5..2c640da058e 100644 --- a/testing/Bootstrap.ps1 +++ b/testing/Bootstrap.ps1 @@ -77,4 +77,4 @@ if ($?) Write-Host "Connecting the cluster to Arc with connectedk8s..." $Env:KUBECONFIG="$PSScriptRoot/tmp/KUBECONFIG" $Env:HELMVALUESPATH="$PSScriptRoot/bin/connectedk8s-values.yaml" -az connectedk8s connect -g $ENVCONFIG.resourceGroup -n $ENVCONFIG.arcClusterName -l uksouth +az connectedk8s connect -g $ENVCONFIG.resourceGroup -n $ENVCONFIG.arcClusterName -l centraluseuap diff --git a/testing/pipeline/k8s-custom-pipelines.yml b/testing/pipeline/k8s-custom-pipelines.yml index f45dc7894e3..a9dfe074d67 100644 --- a/testing/pipeline/k8s-custom-pipelines.yml +++ b/testing/pipeline/k8s-custom-pipelines.yml @@ -32,6 +32,10 @@ stages: parameters: jobName: Kustomization_FluxConfigurationTests path: ./test/configurations/FluxKustomization.*.Tests.ps1 + - template: ./templates/run-test.yml + parameters: + jobName: OCIRepository_FluxConfigurationTests + path: ./test/configurations/FluxOCIRepository.*.Tests.ps1 - job: BuildPublishExtension pool: vmImage: 'ubuntu-latest' @@ -41,9 +45,9 @@ stages: EXTENSION_NAME: "k8s-configuration" steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.10' + displayName: 'Use Python 3.13' inputs: - versionSpec: 3.10 + versionSpec: 3.13 - bash: | set -ev echo "Building extension ${EXTENSION_NAME}..." @@ -78,9 +82,9 @@ stages: vmImage: 'ubuntu-latest' steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.10' + displayName: 'Use Python 3.13' inputs: - versionSpec: 3.10 + versionSpec: 3.13 - bash: | set -ev diff --git a/testing/pipeline/templates/run-test.yml b/testing/pipeline/templates/run-test.yml index 881c64aedd6..ac4b8286899 100644 --- a/testing/pipeline/templates/run-test.yml +++ b/testing/pipeline/templates/run-test.yml @@ -19,9 +19,9 @@ jobs: kubectl version --client displayName: "Setup the VM with helm3 and kubectl" - task: UsePythonVersion@0 - displayName: 'Use Python 3.10' + displayName: 'Use Python 3.13' inputs: - versionSpec: 3.10 + versionSpec: 3.13 - bash: | set -ev echo "Building extension ${EXTENSION_NAME}..." @@ -64,7 +64,9 @@ jobs: displayName: "Generate a settings.json file" - bash : | echo "Downloading the kind script" - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64 + # Get the latest version tag and download + LATEST_KIND_VERSION=$(curl -s https://api.github.com/repos/kubernetes-sigs/kind/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + curl -Lo ./kind "https://kind.sigs.k8s.io/dl/${LATEST_KIND_VERSION}/kind-linux-amd64" chmod +x ./kind ./kind create cluster displayName: "Create and Start the Kind cluster" diff --git a/testing/test/configurations/Flux.CrossKind.Tests.ps1 b/testing/test/configurations/Flux.CrossKind.Tests.ps1 index b311b6cf5d0..e6aee329b51 100644 --- a/testing/test/configurations/Flux.CrossKind.Tests.ps1 +++ b/testing/test/configurations/Flux.CrossKind.Tests.ps1 @@ -31,7 +31,7 @@ Describe 'Bucket Flux Configuration Testing' { } It "Performs an update on the configuration changing the kind from Bucket to Git" { - $output = az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --kind git -u "https://github.com/Azure/arc-k8s-demo" --branch main --no-wait + $output = az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --kind git -u "https://github.com/AzureArcForKubernetes/arc-k8s-demo" --branch main --no-wait $? | Should -BeTrue # Loop and retry until the configuration installs diff --git a/testing/test/configurations/Flux.HTTPS.Tests.ps1 b/testing/test/configurations/Flux.HTTPS.Tests.ps1 index b5de6ab34ad..91b52355fdc 100644 --- a/testing/test/configurations/Flux.HTTPS.Tests.ps1 +++ b/testing/test/configurations/Flux.HTTPS.Tests.ps1 @@ -9,7 +9,7 @@ Describe 'Flux Configuration (HTTPS) Testing' { } It 'Creates a configuration with https user and https key on the cluster' { - $output = az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/Azure/arc-k8s-demo" -n $configurationName --scope cluster --https-user $dummyValue --https-key $dummyValue --namespace $configurationName --branch main --no-wait + $output = az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/AzureArcForKubernetes/arc-k8s-demo" -n $configurationName --scope cluster --https-user $dummyValue --https-key $dummyValue --namespace $configurationName --branch main --no-wait $? | Should -BeTrue # Loop and retry until the configuration installs and helm pod comes up diff --git a/testing/test/configurations/Flux.PrivateKey.Tests.ps1 b/testing/test/configurations/Flux.PrivateKey.Tests.ps1 index 0b334b3e633..a17c3723a68 100644 --- a/testing/test/configurations/Flux.PrivateKey.Tests.ps1 +++ b/testing/test/configurations/Flux.PrivateKey.Tests.ps1 @@ -22,7 +22,7 @@ Describe 'Flux Configuration (SSH Configs) Testing' { } $SSH_GIT_URL = "ssh://github.com/anubhav929/flux-get-started.git" - $HTTP_GIT_URL = "https://github.com/Azure/arc-k8s-demo" + $HTTP_GIT_URL = "https://github.com/AzureArcForKubernetes/arc-k8s-demo" $configDataRSA = [System.Tuple]::Create("rsa-config", $RSA_KEYPATH) $configDataECDSA = [System.Tuple]::Create("ecdsa-config", $ECDSA_KEYPATH) diff --git a/testing/test/configurations/Flux.Provider.Tests.ps1 b/testing/test/configurations/Flux.Provider.Tests.ps1 index d05b2984ed0..b6f2286b559 100644 --- a/testing/test/configurations/Flux.Provider.Tests.ps1 +++ b/testing/test/configurations/Flux.Provider.Tests.ps1 @@ -6,7 +6,7 @@ Describe 'Flux Configuration Testing with provider' { } It 'Creates a configuration with provider and checks that it onboards correctly' { - az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/Azure/arc-k8s-demo" -n $configurationName --scope cluster --namespace $configurationName --branch main --no-wait --provider azure + az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/AzureArcForKubernetes/arc-k8s-demo" -n $configurationName --scope cluster --namespace $configurationName --branch main --no-wait --provider azure $? | Should -BeTrue # Loop and retry until the configuration installs diff --git a/testing/test/configurations/Flux.Tests.ps1 b/testing/test/configurations/Flux.Tests.ps1 index 5d87fe7e31f..cb1004f4670 100644 --- a/testing/test/configurations/Flux.Tests.ps1 +++ b/testing/test/configurations/Flux.Tests.ps1 @@ -6,7 +6,7 @@ Describe 'Basic Flux Configuration Testing' { } It 'Creates a configuration and checks that it onboards correctly' { - az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/Azure/arc-k8s-demo" -n $configurationName --scope cluster --namespace $configurationName --branch main --no-wait + az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/AzureArcForKubernetes/arc-k8s-demo" -n $configurationName --scope cluster --namespace $configurationName --branch main --no-wait $? | Should -BeTrue # Loop and retry until the configuration installs @@ -32,7 +32,7 @@ Describe 'Basic Flux Configuration Testing' { } It "Performs a re-PUT of the configuration on the cluster, with HTTPS in caps" { - az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "HTTPS://github.com/Azure/arc-k8s-demo" -n $configurationName --scope cluster --namespace $configurationName --branch main --no-wait + az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/AzureArcForKubernetes/arc-k8s-demo" -n $configurationName --scope cluster --namespace $configurationName --branch main --no-wait $? | Should -BeTrue } diff --git a/testing/test/configurations/FluxKustomization.Wait.Tests.ps1 b/testing/test/configurations/FluxKustomization.Wait.Tests.ps1 index bfcab371fb3..b4db378a7f2 100644 --- a/testing/test/configurations/FluxKustomization.Wait.Tests.ps1 +++ b/testing/test/configurations/FluxKustomization.Wait.Tests.ps1 @@ -7,7 +7,7 @@ Describe 'Basic Flux Configuration Testing' { } It 'Creates a configuration for testing default wait value' { - az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/Azure/gitops-flux2-kustomize-helm-mt" -n $configurationName --scope cluster --namespace $configurationName --branch main --kustomization name=infra path=./infrastructure prune=true --no-wait + az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/AzureArcForKubernetes/flux2-kustomize-helm-example" -n $configurationName --scope cluster --namespace $configurationName --branch namespace-scope --kustomization name=infra path=./infrastructure prune=true --no-wait $? | Should -BeTrue # Loop and retry until the configuration installs @@ -32,7 +32,7 @@ Describe 'Basic Flux Configuration Testing' { } It "Performs a re-PUT of the configuration on the cluster, with health check disabled for kustomization" { - az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/Azure/gitops-flux2-kustomize-helm-mt" -n $configurationName --kustomization name=infra path=./infrastructure disable-health-check=true --no-wait + az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/AzureArcForKubernetes/flux2-kustomize-helm-example" -n $configurationName --kustomization name=infra path=./infrastructure disable-health-check=true --no-wait $? | Should -BeTrue # Loop and retry until the configuration installs @@ -118,7 +118,7 @@ Describe 'Basic Flux Configuration Testing' { } It 'Creates a configuration for testing with health check disabled for kustomization' { - az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/Azure/gitops-flux2-kustomize-helm-mt" -n $secondConfig --scope cluster --namespace $secondConfig --branch main --kustomization name=infra path=./infrastructure prune=true disable-health-check=true --no-wait + az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -u "https://github.com/AzureArcForKubernetes/flux2-kustomize-helm-example" -n $secondConfig --scope cluster --namespace $secondConfig --branch namespace-scope --kustomization name=infra path=./infrastructure prune=true disable-health-check=true --no-wait $? | Should -BeTrue # Loop and retry until the configuration installs diff --git a/testing/test/configurations/FluxOCIRepository.Insecure.Tests.ps1 b/testing/test/configurations/FluxOCIRepository.Insecure.Tests.ps1 new file mode 100644 index 00000000000..e385fbad1a6 --- /dev/null +++ b/testing/test/configurations/FluxOCIRepository.Insecure.Tests.ps1 @@ -0,0 +1,90 @@ +Describe 'Flux Configuration (OCI Repository - Insecure Mode) Testing' { + BeforeAll { + . $PSScriptRoot/Constants.ps1 + . $PSScriptRoot/Helper.ps1 + + $url = "oci://ghcr.io/stefanprodan/manifests/podinfo" + $configurationName = "oci-insecure-config" + $tag = "latest" + } + + It 'Creates a configuration with insecure mode enabled on the cluster' { + az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --namespace $configurationName --scope cluster --kind oci -u $url --tag $tag --oci-insecure --kustomization name=insecuretest path=./ prune=true disable-health-check=true --no-wait + $? | Should -BeTrue + + $n = 0 + do { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + if ($?) { + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $urlReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("url").GetString() + $tagReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("repositoryRef").GetProperty("tag").GetString() + $insecureFlag = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("insecure").GetBoolean() + + Write-Host "Provisioning State: $provisioningState" + Write-Host "OCI Repository URL: $urlReturned" + Write-Host "OCI Repository Tag: $tagReturned" + Write-Host "Insecure Flag: $insecureFlag" + + if ($provisioningState -eq $SUCCEEDED -and $insecureFlag -eq $true -and $urlReturned -eq $url -and $tagReturned -eq $tag) { + break + } + } else { + Write-Host "Show command failed (attempt $n)." + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It 'Toggle the insecure setting for ociRepo' { + az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --kind oci --oci-insecure=false --no-wait + $? | Should -BeTrue + + $n = 0 + do { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + if ($?) { + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $ociRepo = $jsonOutput.RootElement.GetProperty("ociRepository") + + $insecureFlag = $false + $hasProp = $false + try { + $insecureFlag = $ociRepo.GetProperty("insecure").GetBoolean() + $hasProp = $true + } catch { $hasProp = $false } + + Write-Host "Provisioning State: $provisioningState" + Write-Host "Insecure Present: $hasProp Value: $insecureFlag" + + # Accept either explicit false or property removed (treated as secure) + if ($provisioningState -eq $SUCCEEDED -and (-not $hasProp -or ($hasProp -and -not $insecureFlag))) { + break + } + } else { + Write-Host "Show command failed (attempt $n)." + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It 'Deletes the configuration from the cluster' { + az k8s-configuration flux delete -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --force + $? | Should -BeTrue + + az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $? | Should -BeFalse + } + + It 'Performs another list after the delete' { + $output = az k8s-configuration flux list -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" + $configExists = $output | ConvertFrom-Json | Where-Object { $_.id -Match $configurationName } + $configExists | Should -BeNullOrEmpty + } +} \ No newline at end of file diff --git a/testing/test/configurations/FluxOCIRepository.LayerSelector.Tests.ps1 b/testing/test/configurations/FluxOCIRepository.LayerSelector.Tests.ps1 new file mode 100644 index 00000000000..30797bd1ded --- /dev/null +++ b/testing/test/configurations/FluxOCIRepository.LayerSelector.Tests.ps1 @@ -0,0 +1,77 @@ +Describe 'Flux Configuration (OCI Repository - Layer Selector) Testing' { + BeforeAll { + . $PSScriptRoot/Constants.ps1 + . $PSScriptRoot/Helper.ps1 + + $url = "oci://ghcr.io/stefanprodan/manifests/podinfo" + $configurationName = "oci-service-account-config" + $tag = "latest" + $mediaType = "application/vnd.cncf.helm.chart.content.v1.tar+gzip" + $operation = "extract" + } + + It 'Creates a configuration with layer selector configured for oci artifact' { + $output = az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --namespace $configurationName --scope cluster --kind oci -u $url --tag $tag --oci-media-type $mediaType --oci-operation $operation --kustomization name=workloadtest path=./ prune=true --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $ociMediaType = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("layerSelector").GetProperty("mediaType").GetString() + $ociOperation = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("layerSelector").GetProperty("operation").GetString() + Write-Host "Provisioning State: $provisioningState" + Write-Host "OCI Media Type: $ociMediaType" + Write-Host "OCI Operation: $ociOperation" + if ($provisioningState -eq $SUCCEEDED -and $ociMediaType -eq $mediaType -and $ociOperation -eq $operation) { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Update layer selector values for oci artifact for the flux configurations on the cluster" { + $mediaType = "application/vnd.cncf.helm.chart.content.v2.tar+gzip" + $operation = "copy" + $output = az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --kind oci --oci-media-type $mediaType --oci-operation $operation --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $ociMediaType = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("layerSelector").GetProperty("mediaType").GetString() + $ociOperation = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("layerSelector").GetProperty("operation").GetString() + Write-Host "Provisioning State: $provisioningState" + Write-Host "OCI Media Type: $ociMediaType" + Write-Host "OCI Operation: $ociOperation" + if ($provisioningState -eq $SUCCEEDED -and $ociMediaType -eq $mediaType -and $ociOperation -eq $operation) { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Deletes the configuration from the cluster" { + az k8s-configuration flux delete -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --force + $? | Should -BeTrue + + # Configuration should be removed from the resource model + az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $? | Should -BeFalse + } + + It "Performs another list after the delete" { + $output = az k8s-configuration flux list -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" + $configExists = $output | ConvertFrom-Json | Where-Object { $_.id -Match $configurationName } + $configExists | Should -BeNullOrEmpty + } +} \ No newline at end of file diff --git a/testing/test/configurations/FluxOCIRepository.ServiceAccount.Tests.ps1 b/testing/test/configurations/FluxOCIRepository.ServiceAccount.Tests.ps1 new file mode 100644 index 00000000000..cd7f02216b4 --- /dev/null +++ b/testing/test/configurations/FluxOCIRepository.ServiceAccount.Tests.ps1 @@ -0,0 +1,69 @@ +Describe 'Flux Configuration (OCI Repository - Service Account) Testing' { + BeforeAll { + . $PSScriptRoot/Constants.ps1 + . $PSScriptRoot/Helper.ps1 + + $url = "oci://ghcr.io/stefanprodan/manifests/podinfo" + $configurationName = "oci-service-account-config" + $tag = "latest" + } + + It 'Creates a configuration with Service Account auth' { + $output = az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --namespace $configurationName --scope cluster --kind oci -u $url --tag $tag --service-account-name "flux-sa" --kustomization name=workloadtest path=./ prune=true --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $serviceAccountName = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("serviceAccountName").GetString() + Write-Host "Provisioning State: $provisioningState" + Write-Host "Service Account Name: $serviceAccountName" + if ($provisioningState -eq $SUCCEEDED -and $serviceAccountName -eq "flux-sa") { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Update service-account-name for the flux configurations on the cluster" { + $output = az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --kind oci --service-account-name "flux-sa2" --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $serviceAccountName = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("serviceAccountName").GetString() + Write-Host "Provisioning State: $provisioningState" + Write-Host "Service Account Name: $serviceAccountName" + if ($provisioningState -eq $SUCCEEDED -and $serviceAccountName -eq "flux-sa2") { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Deletes the configuration from the cluster" { + az k8s-configuration flux delete -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --force + $? | Should -BeTrue + + # Configuration should be removed from the resource model + az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $? | Should -BeFalse + } + + It "Performs another list after the delete" { + $output = az k8s-configuration flux list -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" + $configExists = $output | ConvertFrom-Json | Where-Object { $_.id -Match $configurationName } + $configExists | Should -BeNullOrEmpty + } +} \ No newline at end of file diff --git a/testing/test/configurations/FluxOCIRepository.TlsConfig.Tests.ps1 b/testing/test/configurations/FluxOCIRepository.TlsConfig.Tests.ps1 new file mode 100644 index 00000000000..cd4bb764226 --- /dev/null +++ b/testing/test/configurations/FluxOCIRepository.TlsConfig.Tests.ps1 @@ -0,0 +1,77 @@ +Describe 'Flux Configuration (OCI Repository - Tls Config) Testing' { + BeforeAll { + . $PSScriptRoot/Constants.ps1 + . $PSScriptRoot/Helper.ps1 + + $url = "oci://ghcr.io/stefanprodan/manifests/podinfo" + $configurationName = "oci-tls-config" + $tag = "latest" + $tlsClientCertificate = "Y2xpZW50Q2VydGlmaWNhdGU=" + $tlsPrivateKey = "cHJpdmF0ZUtleQ==" + $tlsCaCertificate = "Y2FDZXJ0aWZpY2F0ZQ==" + } + + It 'Creates a configuration with Tls Config Auth' { + $output = az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --namespace $configurationName --scope cluster --kind oci -u $url --tag $tag --tls-ca-certificate $tlsCaCertificate --tls-private-key $tlsPrivateKey --tls-client-certificate $tlsClientCertificate --kustomization name=workloadtest path=./ prune=true --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $clientCertificate = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("tlsConfig").GetProperty("clientCertificate").GetString() + $privateKey = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("tlsConfig").GetProperty("privateKey").GetString() + $caCertificate = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("tlsConfig").GetProperty("caCertificate").GetString() + Write-Host "Provisioning State: $provisioningState" + Write-Host "Client Certificate: $clientCertificate" + Write-Host "Private Key: $privateKey" + Write-Host "CA Certificate: $caCertificate" + if ($provisioningState -eq $SUCCEEDED -and $clientCertificate -eq "" -and $privateKey -eq "" -and $caCertificate -eq "") { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Update caCertificate for the flux configurations on the cluster" { + $tlsCaCertificate = "YWFDZXJ0aWZpY2F0ZU5ldw==" + $output = az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --kind oci --tls-ca-certificate $tlsCaCertificate --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $caCertificate = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("tlsConfig").GetProperty("caCertificate").GetString() + Write-Host "Provisioning State: $provisioningState" + Write-Host "CA Certificate: $caCertificate" + if ($provisioningState -eq $SUCCEEDED -and $caCertificate -eq "") { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Deletes the configuration from the cluster" { + az k8s-configuration flux delete -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --force + $? | Should -BeTrue + + # Configuration should be removed from the resource model + az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $? | Should -BeFalse + } + + It "Performs another list after the delete" { + $output = az k8s-configuration flux list -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" + $configExists = $output | ConvertFrom-Json | Where-Object { $_.id -Match $configurationName } + $configExists | Should -BeNullOrEmpty + } +} \ No newline at end of file diff --git a/testing/test/configurations/FluxOCIRepository.Verify.Tests.ps1 b/testing/test/configurations/FluxOCIRepository.Verify.Tests.ps1 new file mode 100644 index 00000000000..30606baa9bb --- /dev/null +++ b/testing/test/configurations/FluxOCIRepository.Verify.Tests.ps1 @@ -0,0 +1,175 @@ +Describe 'Flux Configuration (OCI Repository - Verification) Testing' { + BeforeAll { + . $PSScriptRoot/Constants.ps1 + . $PSScriptRoot/Helper.ps1 + + $url = "oci://ghcr.io/stefanprodan/manifests/podinfo" + $configurationName = "oci-verification-config" + $tag = "latest" + $provider = "cosign" + $issuer = "https://token.actions.githubusercontent.com$" + $subject = "https://github.com/stefanprodan/podinfo.*$" + $verificationConfigKey = "verifyKeys" + $verificationConfigValue = "Y2xpZW50Q2VydGlmaWNhdGU=" + } + + It 'Creates a configuration with OCI verification enabled on the cluster' { + $oidcIdentityJsonSafe = '{"issuer":"' + $issuer + '","subject":"' + $subject + '"}' + Write-Host "Safe OIDC Identity JSON: $oidcIdentityJsonSafe" + + # Create configuration with verification settings + $output = az k8s-configuration flux create ` + -c $ENVCONFIG.arcClusterName ` + -g $ENVCONFIG.resourceGroup ` + --cluster-type "connectedClusters" ` + -n $configurationName ` + --namespace $configurationName ` + --scope cluster ` + --kind oci ` + -u $url ` + --tag $tag ` + --verification-provider $provider ` + --match-oidc-identity $oidcIdentityJsonSafe ` + --verification-config "$verificationConfigKey=$verificationConfigValue" ` + --kustomization name=verificationtest path=./ prune=true ` + --no-wait + + $? | Should -BeTrue + + $n = 0 + do + { + Start-Sleep -Seconds 10 + $output = az k8s-configuration flux show ` + -c $ENVCONFIG.arcClusterName ` + -g $ENVCONFIG.resourceGroup ` + --cluster-type "connectedClusters" ` + -n $configurationName 2>$null + if ($?) { + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $urlReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("url").GetString() + $tagReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("repositoryRef").GetProperty("tag").GetString() + + $verifyElement = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("verify") + $providerReturned = $verifyElement.GetProperty("provider").GetString() + $matchOidcIdentity = $verifyElement.GetProperty("matchOidcIdentity")[0] + $issuerReturned = $matchOidcIdentity.GetProperty("issuer").GetString() + $subjectReturned = $matchOidcIdentity.GetProperty("subject").GetString() + $verificationConfigReturned = $verifyElement.GetProperty("verificationConfig").GetProperty($verificationConfigKey).GetString() + + Write-Host "[POLL $n] State: $provisioningState | URL: $urlReturned | Tag: $tagReturned" + Write-Host " Provider: $providerReturned | Issuer: $issuerReturned" + Write-Host " Subject: $subjectReturned | VerifyKey: $verificationConfigReturned" + + if ($provisioningState -eq $SUCCEEDED) { + if (!$firstSucceededTime) { + $firstSucceededTime = Get-Date + Write-Host "[MILESTONE] First Succeeded at: $firstSucceededTime" -ForegroundColor Cyan + } + + if ($urlReturned -eq $url -and + $tagReturned -eq $tag -and + $providerReturned -eq $provider -and + $issuerReturned -eq $issuer -and + $subjectReturned -eq $subject -and + $verificationConfigReturned -eq "") { + Write-Host "[SUCCESS] All properties match!" -ForegroundColor Green + break + } + } + } else { + Write-Host "[POLL $n] Show command failed, retrying..." + } + + $n++ + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Updates verification settings for the flux configuration on the cluster" { + # Update with new verification settings + $newTag = "1.2.0" + $newUrl = "oci://ghcr.io/stefanprodan/manifests/podinfo2" + $newProvider = "cosign" + $newIssuer = "https://accounts.google.com" + $newSubject = "https://github.com/example/repo/.github/workflows/build.yml@refs/heads/main" + $newVerificationConfigKey = "verifyKeys" + $newVerificationConfigValue = "Y2FDZXJ0aWZpY2F0ZU5ldw==" + + $newOidcIdentityJsonSafe = '{"issuer":"' + $newIssuer + '","subject":"' + $newSubject + '"}' + Write-Host "Safe OIDC Identity JSON: $newOidcIdentityJsonSafe" + + $output = az k8s-configuration flux update ` + -c $ENVCONFIG.arcClusterName ` + -g $ENVCONFIG.resourceGroup ` + --cluster-type "connectedClusters" ` + -n $configurationName ` + --kind oci ` + -u $newUrl ` + --tag $newTag ` + --verification-provider $newProvider ` + --match-oidc-identity $newOidcIdentityJsonSafe ` + --verification-config "$newVerificationConfigKey=$newVerificationConfigValue" ` + --no-wait 2>&1 + + Write-Host "" + Write-Host "Update command output:" -ForegroundColor Cyan + Write-Host $output + + $n = 0 + do + { + Start-Sleep -Seconds 10 + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $urlReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("url").GetString() + $tagReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("repositoryRef").GetProperty("tag").GetString() + + # Check updated verification properties + $verifyElement = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("verify") + $providerReturned = $verifyElement.GetProperty("provider").GetString() + $matchOidcIdentity = $verifyElement.GetProperty("matchOidcIdentity")[0] + $issuerReturned = $matchOidcIdentity.GetProperty("issuer").GetString() + $subjectReturned = $matchOidcIdentity.GetProperty("subject").GetString() + $verificationConfigReturned = $verifyElement.GetProperty("verificationConfig").GetProperty($newVerificationConfigKey).GetString() + + Write-Host "Provisioning State: $provisioningState" + Write-Host "OCI Repository URL: $urlReturned" + Write-Host "OCI Repository Tag: $tagReturned" + Write-Host "Verification Provider: $providerReturned" + Write-Host "OIDC Issuer: $issuerReturned" + Write-Host "OIDC Subject: $subjectReturned" + Write-Host "Verification Config Key: $verificationConfigReturned" + + if ($provisioningState -eq $SUCCEEDED -and + $urlReturned -eq $newUrl -and + $tagReturned -eq $newTag -and + $providerReturned -eq $newProvider -and + $issuerReturned -eq $newIssuer -and + $subjectReturned -eq $newSubject -and + $verificationConfigReturned -eq "") { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Deletes the configuration from the cluster" { + az k8s-configuration flux delete -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --force + $? | Should -BeTrue + + # Configuration should be removed from the resource model + az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $? | Should -BeFalse + } + + It "Performs another list after the delete" { + $output = az k8s-configuration flux list -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" + $configExists = $output | ConvertFrom-Json | Where-Object { $_.id -Match $configurationName } + $configExists | Should -BeNullOrEmpty + } +} \ No newline at end of file diff --git a/testing/test/configurations/FluxOCIRepository.WorkloadIdentity.Tests.ps1 b/testing/test/configurations/FluxOCIRepository.WorkloadIdentity.Tests.ps1 new file mode 100644 index 00000000000..10a3247cb47 --- /dev/null +++ b/testing/test/configurations/FluxOCIRepository.WorkloadIdentity.Tests.ps1 @@ -0,0 +1,79 @@ +Describe 'Flux Configuration (OCI Repository - Workload Identity) Testing' { + BeforeAll { + . $PSScriptRoot/Constants.ps1 + . $PSScriptRoot/Helper.ps1 + + $url = "oci://ghcr.io/stefanprodan/manifests/podinfo" + $configurationName = "oci-workload-identity-config" + $tag = "latest" + } + + It 'Creates a configuration with Workload Identity enabled on the cluster' { + $output = az k8s-configuration flux create -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --namespace $configurationName --scope cluster --kind oci -u $url --tag $tag --use-workload-identity --kustomization name=workloadtest path=./ prune=true disable-health-check=true --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $urlReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("url").GetString() + $tagReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("repositoryRef").GetProperty("tag").GetString() + $workloadIdentityStatus = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("useWorkloadIdentity").GetBoolean() + Write-Host "Provisioning State: $provisioningState" + Write-Host "OCI Repository URL: $urlReturned" + Write-Host "OCI Repository Tag: $tagReturned" + Write-Host "Workload Identity Status: $workloadIdentityStatus" + if ($provisioningState -eq $SUCCEEDED -and $workloadIdentityStatus -eq $true -and $urlReturned -eq $url -and $tagReturned -eq $tag) { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Update tag, url and disable Workload Identity for the flux configurations on the cluster" { + $newTag = "1.2.0" + $newUrl = "oci://ghcr.io/stefanprodan/manifests/podinfo2" + $output = az k8s-configuration flux update -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --kind oci -u $newUrl --tag $newTag --use-workload-identity=false --no-wait + $? | Should -BeTrue + + $n = 0 + do + { + $output = az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $jsonOutput = [System.Text.Json.JsonDocument]::Parse($output) + $provisioningState = ($output | ConvertFrom-Json).provisioningState + $urlReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("url").GetString() + $tagReturned = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("repositoryRef").GetProperty("tag").GetString() + $workloadIdentityStatus = $jsonOutput.RootElement.GetProperty("ociRepository").GetProperty("useWorkloadIdentity").GetBoolean() + Write-Host "Provisioning State: $provisioningState" + Write-Host "OCI Repository URL: $urlReturned" + Write-Host "OCI Repository Tag: $tagReturned" + Write-Host "Workload Identity Status: $workloadIdentityStatus" + if ($provisioningState -eq $SUCCEEDED -and $workloadIdentityStatus -eq $false -and $urlReturned -eq $newUrl -and $tagReturned -eq $newTag) { + break + } + Start-Sleep -Seconds 10 + $n += 1 + } while ($n -le $MAX_RETRY_ATTEMPTS) + $n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS + } + + It "Deletes the configuration from the cluster" { + az k8s-configuration flux delete -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName --force + $? | Should -BeTrue + + # Configuration should be removed from the resource model + az k8s-configuration flux show -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" -n $configurationName + $? | Should -BeFalse + } + + It "Performs another list after the delete" { + $output = az k8s-configuration flux list -c $ENVCONFIG.arcClusterName -g $ENVCONFIG.resourceGroup --cluster-type "connectedClusters" + $configExists = $output | ConvertFrom-Json | Where-Object { $_.id -Match $configurationName } + $configExists | Should -BeNullOrEmpty + } +} \ No newline at end of file