This is an automated email from the ASF dual-hosted git repository. squakez pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit 5b48717870fec73b6f5547e374b5ef36b61571cf Author: Pasquale Congiusti <[email protected]> AuthorDate: Sat Sep 26 10:42:52 2026 +0200 feat(ci): use dev container registry --- .github/actions/registry-setting/action.yml | 4 +- .github/workflows/cert-manager.yml | 12 +- .github/workflows/common.yml | 28 +- .github/workflows/gateway.yml | 12 +- .github/workflows/install.yml | 11 - .github/workflows/kafka.yml | 12 +- .github/workflows/knative.yml | 12 +- .github/workflows/native.yml | 12 +- .github/workflows/nightly-install-olm.yml | 11 - .github/workflows/nightly-multi.yml | 18 +- .github/workflows/telemetry.yml | 12 +- .../ROOT/pages/contributing/developers.adoc | 3 +- .../ROOT/pages/installation/installation.adoc | 43 +-- docs/modules/ROOT/pages/installation/registry.adoc | 13 +- e2e/advanced/environment_test.go | 10 - e2e/install/helm/setup_test.go | 11 - e2e/install/kustomize/all_namespaces_test.go | 2 +- e2e/install/kustomize/single_namespace_test.go | 2 +- e2e/support/test_support.go | 17 +- helm/camel-k/README.md | 28 +- helm/camel-k/templates/operator-deployment.yaml | 22 +- helm/camel-k/templates/rbacs-common.yaml | 69 +++++ pkg/cmd/operator/operator.go | 58 ++-- pkg/install/registry.go | 161 +++++++---- pkg/install/registry_test.go | 313 +++++++++++++++++++++ pkg/resources/config/rbac/dev-registry-role.yaml | 44 +++ pkg/util/kubernetes/docker_secret.go | 20 +- 27 files changed, 665 insertions(+), 295 deletions(-) diff --git a/.github/actions/registry-setting/action.yml b/.github/actions/registry-setting/action.yml index b0a6cade9..6c9adc983 100644 --- a/.github/actions/registry-setting/action.yml +++ b/.github/actions/registry-setting/action.yml @@ -16,7 +16,7 @@ # --------------------------------------------------------------------------- name: registry-setting -description: 'Setting registry' +description: 'Setting registry externally' runs: using: "composite" @@ -30,7 +30,7 @@ runs: # Create a self-signed certificate openssl req -x509 -newkey rsa:2048 -nodes -keyout registry.key -out registry.crt -days 7 -subj "/CN=registry" kubectl create secret tls registry-tls --cert=registry.crt --key=registry.key -n registry - kubectl apply -f .github/actions/registry-setting/registry.yaml -n registry + kubectl apply -f .github/actions/ext-registry-setting/registry.yaml -n registry kubectl wait --for=condition=available deployment/registry -n registry --timeout=60s KAMEL_INSTALL_REGISTRY="$(kubectl -n registry get service registry -o jsonpath='{.spec.clusterIP}')" echo "KAMEL_INSTALL_REGISTRY=$KAMEL_INSTALL_REGISTRY" >> "$GITHUB_ENV" diff --git a/.github/workflows/cert-manager.yml b/.github/workflows/cert-manager.yml index e83e1d46e..7d3acf579 100644 --- a/.github/workflows/cert-manager.yml +++ b/.github/workflows/cert-manager.yml @@ -72,18 +72,12 @@ jobs: run: | ./e2e/cert-manager/setup/setup.sh - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | - make install-k8s-global + kubectl create ns camel-k + kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts + kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s - name: Run test shell: bash diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index 8b2edd1da..b931cb295 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -70,17 +70,20 @@ jobs: - name: Infra setting uses: ./.github/actions/infra-setting - - name: Create operator namespace + - name: Install operator shell: bash run: | kubectl create ns camel-k + # the extra configuration is required to run the container registry with secret + kubectl create configmap camel-k-operator-configmap-configuration \ + --from-literal=ENABLE_DEV_REGISTRY_SECRET="true" \ + --from-literal=MAVEN_REPOSITORIES_ALLOWED="https://maven.repository.redhat.com/ga@id=redhat" \ + -n camel-k + # make the test aware that each Integration requires a pull secret + E2E_TEST_REGISTRY_SECRET_COPY=true + echo "E2E_TEST_REGISTRY_SECRET_COPY=$E2E_TEST_REGISTRY_SECRET_COPY" >> $GITHUB_ENV + echo "Setting E2E_TEST_REGISTRY_SECRET_COPY=true in order to automate the copy of the pull secret and the setting in each test" - - name: Registry setting - uses: ./.github/actions/registry-setting - - - name: Install operator - shell: bash - run: | kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s # Install Apache Kamelets catalog @@ -104,17 +107,6 @@ jobs: - name: Infra setting uses: ./.github/actions/infra-setting - - name: Create registry secret namespace - shell: bash - run: | - kubectl create ns camel-k - E2E_TEST_REGISTRY_CONFIG_COPY=true - echo "E2E_TEST_REGISTRY_CONFIG_COPY=$E2E_TEST_REGISTRY_CONFIG_COPY" >> $GITHUB_ENV - echo "Setting E2E_TEST_REGISTRY_CONFIG_COPY=true in order to automate the copy of the registry config for operator install" - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install CRDs shell: bash run: | diff --git a/.github/workflows/gateway.yml b/.github/workflows/gateway.yml index e3f29074f..8c3b546b8 100644 --- a/.github/workflows/gateway.yml +++ b/.github/workflows/gateway.yml @@ -72,18 +72,12 @@ jobs: run: | ./e2e/gateway/setup/setup.sh - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | - make install-k8s-global + kubectl create ns camel-k + kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts + kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s - name: Run test shell: bash diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 88ca48c9a..4bba6055c 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -85,17 +85,6 @@ jobs: run: | make release-helm - - name: Create registry secret namespace - shell: bash - run: | - kubectl create ns camel-k - E2E_TEST_REGISTRY_CONFIG_COPY=true - echo "E2E_TEST_REGISTRY_CONFIG_COPY=$E2E_TEST_REGISTRY_CONFIG_COPY" >> $GITHUB_ENV - echo "Setting E2E_TEST_REGISTRY_CONFIG_COPY=true in order to automate the copy of the registry config for operator install" - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Run tests shell: bash run: | diff --git a/.github/workflows/kafka.yml b/.github/workflows/kafka.yml index 1481627e1..d3473128a 100644 --- a/.github/workflows/kafka.yml +++ b/.github/workflows/kafka.yml @@ -72,18 +72,12 @@ jobs: run: | ./e2e/kafka/setup/setup.sh - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | - make install-k8s-global + kubectl create ns camel-k + kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts + kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s - name: Run test shell: bash diff --git a/.github/workflows/knative.yml b/.github/workflows/knative.yml index acbe222c4..d42a0ab27 100644 --- a/.github/workflows/knative.yml +++ b/.github/workflows/knative.yml @@ -72,18 +72,12 @@ jobs: run: | ./e2e/knative/files/setup.sh - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | - make install-k8s-global + kubectl create ns camel-k + kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts + kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s # Install Apache Kamelets catalog mvn -q dependency:copy -Dartifact=org.apache.camel.kamelets:camel-kamelets:4.18.1:jar -Dmdep.useBaseVersion=true -DoutputDirectory=/tmp unzip /tmp/camel-kamelets-4.18.1.jar -d /tmp && kubectl apply -f /tmp/kamelets -n camel-k diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index ab1b80de9..13627fd01 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -73,18 +73,12 @@ jobs: - name: Infra setting uses: ./.github/actions/infra-setting - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | - make install-k8s-global + kubectl create ns camel-k + kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts + kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s # Install Apache Kamelets catalog mvn -q dependency:copy -Dartifact=org.apache.camel.kamelets:camel-kamelets:4.18.1:jar -Dmdep.useBaseVersion=true -DoutputDirectory=/tmp unzip /tmp/camel-kamelets-4.18.1.jar -d /tmp && kubectl apply -f /tmp/kamelets -n camel-k diff --git a/.github/workflows/nightly-install-olm.yml b/.github/workflows/nightly-install-olm.yml index 9f5682223..eaaee18a0 100644 --- a/.github/workflows/nightly-install-olm.yml +++ b/.github/workflows/nightly-install-olm.yml @@ -73,17 +73,6 @@ jobs: echo "Setting bundle image name as as $BUNDLE_IMAGE_NAME which is required by OLM tests" BUNDLE_IMAGE_NAME=docker.io/testcamelk/camel-k-bundle make bundle-push - - name: Create registry secret namespace - shell: bash - run: | - kubectl create ns camel-k - E2E_TEST_REGISTRY_CONFIG_COPY=true - echo "E2E_TEST_REGISTRY_CONFIG_COPY=$E2E_TEST_REGISTRY_CONFIG_COPY" >> $GITHUB_ENV - echo "Setting E2E_TEST_REGISTRY_CONFIG_COPY=true in order to automate the copy of the registry config for operator install" - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Run tests shell: bash run: | diff --git a/.github/workflows/nightly-multi.yml b/.github/workflows/nightly-multi.yml index 921e7b3e4..329282b4d 100644 --- a/.github/workflows/nightly-multi.yml +++ b/.github/workflows/nightly-multi.yml @@ -48,20 +48,13 @@ jobs: - name: Infra setting uses: ./.github/actions/infra-setting - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | THIS_VERSION="$(make get-version)" VERSION="$(make get-version | sed s/-SNAPSHOT//)-nightly" sed -i "s#apache/camel-k:$THIS_VERSION#testcamelk/camel-k:$VERSION#g" install/base/config/manager/operator-deployment.yaml + kubectl create ns camel-k kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s # Install Apache Kamelets catalog @@ -93,19 +86,12 @@ jobs: - name: Infra setting uses: ./.github/actions/infra-setting - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | VERSION="$(make get-version | sed s/-SNAPSHOT//)-nightly-21-jdk" CUSTOM_IMAGE=testcamelk/camel-k CUSTOM_VERSION=$VERSION make bundle + kubectl create ns camel-k kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s # Install Apache Kamelets catalog diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml index cd0d5fc18..e6af676f5 100644 --- a/.github/workflows/telemetry.yml +++ b/.github/workflows/telemetry.yml @@ -67,18 +67,12 @@ jobs: - name: Infra setting uses: ./.github/actions/infra-setting - - name: Create operator namespace - shell: bash - run: | - kubectl create ns camel-k - - - name: Registry setting - uses: ./.github/actions/registry-setting - - name: Install operator shell: bash run: | - make install-k8s-global + kubectl create ns camel-k + kubectl apply -k install/overlays/all-namespaces/ --server-side --force-conflicts + kubectl wait --for=condition=available deployment/camel-k-operator -n camel-k --timeout=60s - name: Install OTLP Collector uses: ./.github/actions/install-otlp-collector diff --git a/docs/modules/ROOT/pages/contributing/developers.adoc b/docs/modules/ROOT/pages/contributing/developers.adoc index 17c11603b..88703eee7 100644 --- a/docs/modules/ROOT/pages/contributing/developers.adoc +++ b/docs/modules/ROOT/pages/contributing/developers.adoc @@ -165,12 +165,11 @@ If you want to test Helm installation * Build the Helm chart: `make release-helm` * Build the project and the image: `make images` -* Set the internal registry: `export REGISTRY_ADDRESS=$(kubectl -n kube-system get service registry -o jsonpath='{.spec.clusterIP}')` * Install with Helm (look at the latest version produced by `make release-helm`) [source] ---- -helm install camel-k-dev docs/charts/camel-k-2.4.0-SNAPSHOT.tgz --set platform.build.registry.address=${REGISTRY_ADDRESS} --set platform.build.registry.insecure=true --set operator.image=apache/camel-k:2.4.0-SNAPSHOT +helm install camel-k-dev docs/charts/camel-k-2.4.0-SNAPSHOT.tgz --set operator.image=apache/camel-k:2.4.0-SNAPSHOT ---- * To uninstall: `helm uninstall camel-k-dev` diff --git a/docs/modules/ROOT/pages/installation/installation.adoc b/docs/modules/ROOT/pages/installation/installation.adoc index b0fd3cd56..c57f5f6c4 100644 --- a/docs/modules/ROOT/pages/installation/installation.adoc +++ b/docs/modules/ROOT/pages/installation/installation.adoc @@ -3,26 +3,6 @@ Camel K allows us to run Camel integrations directly on a Kubernetes cluster. To use it, you need to be connected to a cloud environment or to a local cluster created for development purposes (ie, Minikube or Kind). -[[registry]] -== Container registry configuration - -You will need a container registry available in order to push and pull the generated Camel applications. The easiest way to configure it is to store the configuration on a Configmap which will be used by the operator: - -``` -$ kubectl create ns camel-k - -$ kubectl create configmap camel-k-operator-configmap-configuration \ - --from-literal=REGISTRY_ADDRESS="docker.io" \ - --from-literal=REGISTRY_SECRET="my-docker-secret" - -n camel-k -``` - -NOTE: the configuration needs to be available in the namespace where the operator will run. - -Have a further look at the xref:installation/registry.adoc[production ready registry configuration documentation]. - -You can now install and run the Camel K operator. You can do it via any of the following methodologies: - [[kustomize]] == Installation via Kustomize @@ -105,6 +85,29 @@ Camel K installation is usually straightforward, but for certain cluster types y - xref:installation/platform/gke.adoc[Google Kubernetes Engine (GKE)] - xref:installation/platform/iks.adoc[IBM Kubernetes Services (IKS)] +[[registry]] +== Development container registry configuration + +Camel K needs a container registry in order to push the generated Camel applications and Kubernetes needs the same registry to pull and run them live. The project offers a default development container registry that will simplify quick starts, demo and testing. When running the project for the first time or in a local environment you shouldn't worry as it will be completely transparent to you. The development registry is controlled through the following environment variables configuration: + +[cols="1,3,2", options="header"] +|=== +| Name | Description | Default Value + +| ENABLE_DEV_REGISTRY +| Run a demo container registry application in the same namespace as the Camel K Operator. +| true + +| ENABLE_DEV_REGISTRY_SECURE +| Whether to secure the demo container registry with a default test user (`admin/password`). In this case you need to provide a secret in the Integration namespace and use the `pull-secret` trait configuration. Be aware that the operator will store a secret named `ck-dev-registry` in the same namespace where it is installed. You may copy and use it in your Integration namespace for local development purposes. +| false + +|=== + +When ready to move to a real enterprise installation you will need to turn the flag off (or remove it) and provide a proper xref:installation/registry.adoc[production ready registry configuration]. + +NOTE: if you don't remove the `ENABLE_DEV_REGISTRY` parameter, the operator will overwrite any registry configuration at startup with its internal demo registry. + [[fine-tuning]] == Fine Tuning diff --git a/docs/modules/ROOT/pages/installation/registry.adoc b/docs/modules/ROOT/pages/installation/registry.adoc index 6ea10eb47..cef72f0e9 100644 --- a/docs/modules/ROOT/pages/installation/registry.adoc +++ b/docs/modules/ROOT/pages/installation/registry.adoc @@ -6,11 +6,9 @@ image::architecture/camel-k-registry.svg[Container registry in action, width=800 The Camel K operator is in charge to build a Camel application and to "containerize" it, storing the result into a container registry. The same registry is used by the cluster to run the Camel application. Basically the operator push the image and the cluster pull it from the same source. -For the reason above it's important that you provide a container registry which is accessible from both the operator Pod and the cluster internal mechanisms. However, a **default registry** is present in certain platforms such as _Minikube_, _Openshift_ or _Docker Desktop_. +For the reason above it's important that you provide a container registry which is accessible from both the operator Pod and the cluster internal mechanisms. However, a **default registry** is available when running quick starts and demos. -For any other platform that do not provide a default container registry, then, a container registry must be provided accordingly. - -You will need to add or edit any existing registry environment variable configuration according your installation method. These are the variables you can configure: +When ready to move to a real enterprise environment, then you're expected to configure the registry. You will need to add or edit any existing registry environment variable configuration according your installation method. These are the variables you can configure: [cols="1,3,2", options="header"] |=== @@ -59,6 +57,13 @@ NOTE: you must include `--docker-server docker.io` value also if you're using Do As each registry may have a slightly different way of securing the access you can use the generic guidelines provided in and adjust accordingly (more information in the xref:installation/registry/registry-secret.adoc[Secret registry configuration] guide). We expect that at the end of the process you have a public address (1) an _organization_ (2) (optional, see details below) and a _secret_ (3) values that will be used to configure the registry. +[[pull-secret]] +=== Deployment pull secret + +When you're using a secured registry protected via secret, you will also need to provide a secret via `pull-secret.secretName` trait parameter. We advice to separate the credentials used by the operator to **push** a container from the credentials required to **pull** the container. The operators requires **write** privileges, whilst the container only requires **read** privileges to get the container. + +The **read** secret you will be using via trait configuration must be available in the same Integration namespace. + [[organization]] === Role of the organization parameter diff --git a/e2e/advanced/environment_test.go b/e2e/advanced/environment_test.go index 2df9018a8..1389c2d4b 100644 --- a/e2e/advanced/environment_test.go +++ b/e2e/advanced/environment_test.go @@ -24,7 +24,6 @@ package advanced import ( "context" - "os" "strings" "testing" @@ -53,17 +52,8 @@ func TestHTTPProxy(t *testing.T) { // Retrieve the Kubernetes Service ClusterIPs to populate the NO_PROXY environment variable svc := Service(t, ctx, TestDefaultNamespace, "kubernetes")() g.Expect(svc).NotTo(BeNil()) - noProxy = append(noProxy, svc.Spec.ClusterIPs...) - // Retrieve the internal container registry to populate the NO_PROXY environment variable - if registry, ok := os.LookupEnv("KAMEL_INSTALL_REGISTRY"); ok { - domain := RegistryRegexp.FindString(registry) - g.Expect(domain).NotTo(BeNil()) - domain = strings.Split(domain, ":")[0] - noProxy = append(noProxy, domain) - } - // Install Camel K with the HTTP proxy environment variable InstallOperatorWithConf(t, ctx, g, ns, "", false, map[string]string{ diff --git a/e2e/install/helm/setup_test.go b/e2e/install/helm/setup_test.go index f43dd1f07..d98f10f5d 100644 --- a/e2e/install/helm/setup_test.go +++ b/e2e/install/helm/setup_test.go @@ -43,9 +43,6 @@ func TestHelmInstallation(t *testing.T) { // as we must make the procedure to install them accordingly g.Expect(CRDs(t)()).Should(BeNil(), "No Camel K CRDs should be previously installed for this test") - registry := os.Getenv("KAMEL_INSTALL_REGISTRY") - g.Expect(registry).NotTo(BeEmpty(), "KAMEL_INSTALL_REGISTRY env var must not be empty") - operatorID := "helm-ck" os.Setenv("CAMEL_K_TEST_MAKE_DIR", "../../../") ExpectExecSucceed(t, g, @@ -54,14 +51,6 @@ func TestHelmInstallation(t *testing.T) { "install", "camel-k", fmt.Sprintf("../../../docs/charts/camel-k-%s.tgz", defaults.Version), - "--set", "operator.env[0].name=REGISTRY_ADDRESS", - "--set", "operator.env[0].value="+registry, - // We expect the testing infra to make it available a secret - // named "my-registry" in the installation namespace - "--set", "operator.env[1].name=REGISTRY_SECRET", - "--set", "operator.env[1].value=my-registry", - "--set", "operator.env[2].name=REGISTRY_INSECURE", - "--set-string", "operator.env[2].value=true", "--set", fmt.Sprintf("operator.operatorId=%s", operatorID), "-n", operatorNs, "--force", diff --git a/e2e/install/kustomize/all_namespaces_test.go b/e2e/install/kustomize/all_namespaces_test.go index 9f25ceb06..63930e8da 100644 --- a/e2e/install/kustomize/all_namespaces_test.go +++ b/e2e/install/kustomize/all_namespaces_test.go @@ -37,7 +37,7 @@ import ( . "github.com/onsi/gomega" ) -func TestKustomizeDescoped(t *testing.T) { +func TestKustomizeAllNamespaces(t *testing.T) { kustomizeDir := testutil.MakeTempCopyDir(t, "../../../install") WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) { // Let's make sure no CRD is yet available in the cluster diff --git a/e2e/install/kustomize/single_namespace_test.go b/e2e/install/kustomize/single_namespace_test.go index 158401a6f..d8187e051 100644 --- a/e2e/install/kustomize/single_namespace_test.go +++ b/e2e/install/kustomize/single_namespace_test.go @@ -47,7 +47,7 @@ func TestKustomizeSingleNamespace(t *testing.T) { WithNamedTestNamespace(t, func(ctx context.Context, g *WithT, tenantNs string) { // Let's make sure no CRD is yet available in the cluster // as we must make the procedure to install them accordingly - g.Eventually(CRDs(t)).Should(BeNil(), "No Camel K CRDs should be previously installed for this test") + g.Expect(CRDs(t)()).Should(BeNil(), "No Camel K CRDs should be previously installed for this test") ExpectExecSucceed(t, g, Kubectl( "apply", "-k", diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go index d0e9f59ef..8543e77bb 100644 --- a/e2e/support/test_support.go +++ b/e2e/support/test_support.go @@ -80,6 +80,7 @@ import ( ) const kubeConfigEnvVar = "KUBECONFIG" +const registrySecret = "ck-dev-registry" var TestDefaultNamespace = "default" @@ -213,7 +214,7 @@ func KamelRunWithID(t *testing.T, ctx context.Context, operatorID string, namesp func kamelRunWithContext(t *testing.T, ctx context.Context, operatorID string, namespace string, args ...string) *cobra.Command { if os.Getenv("E2E_TEST_REGISTRY_SECRET_COPY") == "true" { - args = append(args, "-t", "pull-secret.secret-name=my-registry") + args = append(args, "-t", "pull-secret.secret-name="+registrySecret) } return kamelCommandWithContext(t, ctx, "run", operatorID, namespace, args...) } @@ -1958,12 +1959,7 @@ func WithNamedTestNamespace(t *testing.T, doRun func(context.Context, *gomega.Wi func WithExistingNamedTestNamespace(t *testing.T, doRun func(context.Context, *gomega.WithT, string), namespace string) { // Required to copy the registry secret previously set on the camel-k namespace if os.Getenv("E2E_TEST_REGISTRY_SECRET_COPY") == "true" { - copySecret(t, testContext, TestClient(t), "my-registry", "camel-k", namespace) - } - // Required to copy the registry config previously set on the camel-k namespace - // This is used by "advanced" tests which are installing the operator on their own - if os.Getenv("E2E_TEST_REGISTRY_CONFIG_COPY") == "true" { - copyConfigMap(t, testContext, TestClient(t), "camel-k-operator-configmap-configuration", "camel-k", namespace) + copySecret(t, testContext, TestClient(t), registrySecret, "camel-k", namespace) } invokeUserTestCode(t, testContext, namespace, doRun) @@ -2117,12 +2113,7 @@ func NewNamedTestNamespace(t *testing.T, ctx context.Context, name string) ctrl. // Required to copy the registry secret previously set on the camel-k namespace if os.Getenv("E2E_TEST_REGISTRY_SECRET_COPY") == "true" { - copySecret(t, ctx, c, "my-registry", "camel-k", name) - } - // Required to copy the registry config previously set on the camel-k namespace - // This is used by "advanced" tests which are installing the operator on their own - if os.Getenv("E2E_TEST_REGISTRY_CONFIG_COPY") == "true" { - copyConfigMap(t, ctx, c, "camel-k-operator-configmap-configuration", "camel-k", name) + copySecret(t, ctx, c, registrySecret, "camel-k", name) } return namespaceOrProject diff --git a/helm/camel-k/README.md b/helm/camel-k/README.md index 2ca0f4cac..1b360a328 100644 --- a/helm/camel-k/README.md +++ b/helm/camel-k/README.md @@ -2,24 +2,6 @@ Apache Camel K is the lightweight integration platform for Kubernetes: the easiest way to build and manage your Camel applications on Kubernetes. This chart deploys the Camel K operator and all resources needed to natively run Apache Camel Integrations on any Kubernetes cluster. -## Prerequisites - -- A container image registry installed and configured for pull -- For production environments, a registry secret containing the access to container registry - -### Minikube - -Minikube offers a container registry addon, which it makes very well suited for local Camel K development and testing purposes: - -```bash -$ minikube addons enable registry -... -$ kubectl -n kube-system get service registry -o jsonpath='{.spec.clusterIP}' -a.b.c.d -``` - -You can use the container registry ClusterIP result `a.b.c.d` as `REGISTRY_ADDRESS` configuration. - ## Installation procedure To install the chart, first add the Camel K repository: @@ -30,17 +12,11 @@ $ helm repo add camel-k https://apache.github.io/camel-k/charts ## Install the operator -When installing the operator you must at least include the container registry to use (either the address or the service to use): - ```bash -$ helm install camel-k camel-k/camel-k --set global=true \ - --set operator.env[0].name=REGISTRY_ADDRESS \ - --set operator.env[0].value=<my-registry-address> \ - --set operator.env[1].name=REGISTRY_SECRET \ - --set operator.env[1].value=<my-registry-secret> +$ helm install camel-k camel-k/camel-k --set global=true ``` -**Note**: if you're running a local Minikube installation, you can use the registry ClusterIP as `REGISTRY_ADDRESS`, skip the `REGISTRY_SECRET` and add `REGISTRY_INSECURE=true` environment variables. +The procedure is installing the operator and a side default development container registry you can use for local development. See official documentation to learn how to configure a production grade container registry instead. ## Test your installation diff --git a/helm/camel-k/templates/operator-deployment.yaml b/helm/camel-k/templates/operator-deployment.yaml index 56ea2942f..9a4e8094f 100644 --- a/helm/camel-k/templates/operator-deployment.yaml +++ b/helm/camel-k/templates/operator-deployment.yaml @@ -20,6 +20,7 @@ kind: Deployment metadata: labels: app: camel-k + name: camel-k-operator camel.apache.org/component: operator {{- include "camel-k.labels" . | nindent 4 }} {{- with .Values.operator.annotations }} @@ -64,16 +65,6 @@ spec: value: {{ .Values.operator.logLevel }} - name: OPERATOR_NAME value: camel-k - - # Registry vars - - name: REGISTRY_ADDRESS - value: "" - - name: REGISTRY_SECRET - value: "" - # Set true only for testing purposes - - name: REGISTRY_INSECURE - value: "false" - - name: POD_NAME valueFrom: fieldRef: @@ -82,6 +73,17 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + # Used to automatically enable Camel Monitor operator project monitoring + # Keep the default value or change it to the same label used by the project + # Note: remove the variable to disable the feature. + - name: CAMEL_MONITOR_OPERATOR_LABEL + value: "camel.apache.org/monitor" + # You can provide a different SA for builder Pods + - name: BUILDER_SA + value: "camel-k-builder" + # Demo only: turn it off or remove the variable in a production environment. + - name: ENABLE_DEV_REGISTRY + value: "true" - name: OPERATOR_ID value: {{ .Values.operator.operatorId }} {{- with .Values.operator.extraEnv }} diff --git a/helm/camel-k/templates/rbacs-common.yaml b/helm/camel-k/templates/rbacs-common.yaml index 41b426201..8b794597b 100644 --- a/helm/camel-k/templates/rbacs-common.yaml +++ b/helm/camel-k/templates/rbacs-common.yaml @@ -101,6 +101,61 @@ rules: --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role +metadata: + labels: + app: camel-k + name: {{ include "camel-k.fullname" . }}-dev-registry +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create +- apiGroups: + - "" + resourceNames: + - registry-auth + - registry-tls + - ck-dev-registry + resources: + - secrets + verbs: + - get + - update +- apiGroups: + - apps + resources: + - deployments + verbs: + - create +- apiGroups: + - apps + resourceNames: + - registry + resources: + - deployments + verbs: + - get + - update +- apiGroups: + - "" + resources: + - services + verbs: + - create +- apiGroups: + - "" + resourceNames: + - registry + resources: + - services + verbs: + - get + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role metadata: labels: app: camel-k @@ -145,6 +200,20 @@ subjects: --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding +metadata: + labels: + app: camel-k + name: {{ include "camel-k.fullname" . }}-dev-registry +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "camel-k.fullname" . }}-dev-registry +subjects: +- kind: ServiceAccount + name: {{ include "camel-k.fullname" . }}-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding metadata: labels: app: camel-k diff --git a/pkg/cmd/operator/operator.go b/pkg/cmd/operator/operator.go index 60b181bf8..fe116c981 100644 --- a/pkg/cmd/operator/operator.go +++ b/pkg/cmd/operator/operator.go @@ -66,6 +66,11 @@ import ( logutil "github.com/apache/camel-k/v2/pkg/util/log" ) +const ( + devRegistryselfSignedKey = "/tmp/registry.key" + devRegistryselfSignedCrt = "/tmp/registry.crt" +) + var log = logutil.Log.WithName("cmd") func printVersion() { @@ -241,22 +246,7 @@ func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID devRegistryEnvVal, devReg := os.LookupEnv("ENABLE_DEV_REGISTRY") if devReg && devRegistryEnvVal == "true" { - // Only enable registry protected by secret if configured - devRegistrySecretEnvVal, devRegSecret := os.LookupEnv("ENABLE_DEV_REGISTRY_SECRET") - withSecret := devRegSecret && devRegistrySecretEnvVal == "true" - log.Info("Installing development container registry") - if withSecret { - log.Info("NOTE: ENABLE_DEV_REGISTRY_SECRET is set, mind to provide a secret with the default values in the Integration namespace " + - "and to pull images with it.") - } - log.Info("WARNING: the internal development container registry is ephemeral and not secured. " + - "It MUST be considered for DEVELOPMENT and DEMO purposes only. Make sure to read documentation and switch to " + - "a production grade container registry when moving the operator to a production environment.") - registryCtx, registryCancel := context.WithTimeout(ctx, 1*time.Minute) - defer registryCancel() - if err := install.OperatorStartupRegistry(registryCtx, bootstrapClient, withSecret); err != nil { - log.Error(err, "could not install the development container registry") - } + setupDevContainerRegistry(ctx, bootstrapClient, operatorNamespace) } log.Info("Starting the manager") @@ -327,6 +317,42 @@ func getOperatorImage(ctx context.Context, kubeClient client.Client) (string, er ) } +// setupDevContainerRegistry is in charge to setup the development container registry configuration. +func setupDevContainerRegistry(ctx context.Context, bootstrapClient client.Client, operatorNamespace string) { + // Only enable registry protected by secret if configured + devRegistrySecretEnvVal, devRegSecret := os.LookupEnv("ENABLE_DEV_REGISTRY_SECRET") + withSecret := devRegSecret && devRegistrySecretEnvVal == "true" + log.Info("Installing development container registry") + if withSecret { + log.Info("NOTE: ENABLE_DEV_REGISTRY_SECRET is set, mind to provide a secret with the expected default values (see docs) " + + " in the Integration namespace and to pull images with it.") + } + log.Info("WARNING: the internal development container registry is ephemeral and not secured. " + + "It MUST be considered for DEVELOPMENT and DEMO purposes only. Make sure to read documentation and switch to " + + "a production grade container registry when moving the operator to a production environment.") + registryCtx, registryCancel := context.WithTimeout(ctx, 1*time.Minute) + defer registryCancel() + if err := install.CreateContainerRegistrySelfSignedCerts(ctx, devRegistryselfSignedKey, devRegistryselfSignedCrt); err != nil { + log.Error(err, "could not generate development container registry self signed certificate") + + return + } + crtSecret, err := kubernetes.TLSSecretFromFiles(ctx, operatorNamespace, "registry-tls", devRegistryselfSignedKey, devRegistryselfSignedCrt) + if err != nil { + log.Error(err, "could not generate development container registry self signed certificate secret") + + return + } + overrideConf, err := install.OperatorStartupRegistry(registryCtx, bootstrapClient, withSecret, crtSecret) + if err != nil { + log.Error(err, "could not install the development container registry") + + return + } + + install.OverrideRegistryConfiguration(overrideConf) +} + func exitOnError(err error, msg string) { if err != nil { log.Error(err, msg) diff --git a/pkg/install/registry.go b/pkg/install/registry.go index 460b6a740..1247db3de 100644 --- a/pkg/install/registry.go +++ b/pkg/install/registry.go @@ -34,35 +34,61 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/yaml" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + ctrlcli "sigs.k8s.io/controller-runtime/pkg/client" ) +const serviceRegistryClusterIPTimeout = 30 * time.Second +const serviceRegistryClusterIPPollingTime = 500 * time.Millisecond + +type registryConf struct { + clusterIP string + insecure string + dockerRegistrySecretName string +} + +// CreateContainerRegistrySelfSignedCerts is in charge to generate a self signed certificate for development +// and internal use only. +func CreateContainerRegistrySelfSignedCerts(ctx context.Context, key, crt string) error { + cmd := exec.CommandContext(ctx, "openssl", + "req", "-x509", "-newkey", "rsa:2048", "-nodes", + "-keyout", key, + "-out", crt, + "-days", "365", + "-subj", "/CN=registry", + ) + + return cmd.Run() +} + // OperatorStartupRegistry tries to install optional development container registry. -func OperatorStartupRegistry(ctx context.Context, c client.Client, withSecret bool) error { +func OperatorStartupRegistry(ctx context.Context, c client.Client, withSecret bool, crtSecret *corev1.Secret) (*registryConf, error) { secret, err := resources.Resource("/resources/registry/secret.yaml") if err != nil { - return fmt.Errorf("could not load development container registry Secret configuration: %w", err) + return nil, fmt.Errorf("could not load development container registry Secret configuration: %w", err) } var registrySecret corev1.Secret if err = yaml.Unmarshal(secret, ®istrySecret); err != nil { - return fmt.Errorf("could not parse development container registry Secret configuration: %w", err) + return nil, fmt.Errorf("could not parse development container registry Secret configuration: %w", err) } service, err := resources.Resource("/resources/registry/service.yaml") if err != nil { - return fmt.Errorf("could not load development container registry Service configuration: %w", err) + return nil, fmt.Errorf("could not load development container registry Service configuration: %w", err) } var registryService corev1.Service if err = yaml.Unmarshal(service, ®istryService); err != nil { - return fmt.Errorf("could not parse development container registry Service configuration: %w", err) + return nil, fmt.Errorf("could not parse development container registry Service configuration: %w", err) } deploy, err := resources.Resource("/resources/registry/deploy.yaml") if err != nil { - return fmt.Errorf("could not load development container registry Deployment configuration: %w", err) + return nil, fmt.Errorf("could not load development container registry Deployment configuration: %w", err) } var registryDeploy appsv1.Deployment if err = yaml.Unmarshal(deploy, ®istryDeploy); err != nil { - return fmt.Errorf("could not parse development container registry Deployment configuration: %w", err) + return nil, fmt.Errorf("could not parse development container registry Deployment configuration: %w", err) } if withSecret && len(registryDeploy.Spec.Template.Spec.Containers) > 0 { @@ -83,89 +109,89 @@ func OperatorStartupRegistry(ctx context.Context, c client.Client, withSecret bo }) } + deployNamespace := platform.GetOperatorNamespace() // Get owner reference to operator deployment to manage garbage collection - ref, err := getOwnerRef(ctx, c) + ref, err := getOwnerRef(ctx, c, "camel-k-operator", deployNamespace) if err != nil { - return fmt.Errorf("could not get operator deployment ownership: %w", err) + return nil, fmt.Errorf("could not get operator deployment ownership: %w", err) } - // create self signed certificate - cmd := exec.Command("openssl", - "req", "-x509", "-newkey", "rsa:2048", "-nodes", - "-keyout", "/tmp/registry.key", - "-out", "/tmp/registry.crt", - "-days", "365", - "-subj", "/CN=registry", - ) - - if err = cmd.Run(); err != nil { - return fmt.Errorf("could not generate development container registry self signed certificate: %w", err) - } - crtSecret, err := kubernetes.TLSSecretFromFiles(ctx, "camel-k", "registry-tls", "/tmp/registry.crt", "/tmp/registry.key") - if err != nil { - return fmt.Errorf("could not generate development container registry self signed certificate secret: %w", err) - } crtSecret.SetOwnerReferences([]metav1.OwnerReference{*ref}) - if err := c.Create(ctx, crtSecret); err != nil { - return fmt.Errorf("could not create development container registry push secret: %w", err) + if err := replace(ctx, c, crtSecret); err != nil { + return nil, fmt.Errorf("could not create development container registry push secret: %w", err) } - registrySecret.SetNamespace("camel-k") - registryService.SetNamespace("camel-k") - registryDeploy.SetNamespace("camel-k") + registrySecret.SetNamespace(deployNamespace) + registryService.SetNamespace(deployNamespace) + registryDeploy.SetNamespace(deployNamespace) registrySecret.SetOwnerReferences([]metav1.OwnerReference{*ref}) registryService.SetOwnerReferences([]metav1.OwnerReference{*ref}) registryDeploy.SetOwnerReferences([]metav1.OwnerReference{*ref}) // Try to create the resources now - if err := c.Create(ctx, ®istrySecret); err != nil { - return fmt.Errorf("could not create development container registry Secret configuration: %w", err) + if err := replace(ctx, c, ®istrySecret); err != nil { + return nil, fmt.Errorf("could not create development container registry Secret configuration: %w", err) } - if err := c.Create(ctx, ®istryService); err != nil { - return fmt.Errorf("could not create development container registry Service configuration: %w", err) + if err := replace(ctx, c, ®istryService); err != nil { + return nil, fmt.Errorf("could not create development container registry Service configuration: %w", err) } - if err := c.Create(ctx, ®istryDeploy); err != nil { - return fmt.Errorf("could not create development container registry Deployment configuration: %w", err) + if err := replace(ctx, c, ®istryDeploy); err != nil { + return nil, fmt.Errorf("could not create development container registry Deployment configuration: %w", err) } // Get the cluster IP and use it to configure internally the operator - clusterIP, err := waitForClusterIP(ctx, c, registryService.GetNamespace(), registryService.GetName(), 30*time.Second) + clusterIP, err := waitForClusterIP(ctx, c, registryService.GetNamespace(), registryService.GetName(), serviceRegistryClusterIPTimeout) if err != nil { - return fmt.Errorf("could not get development container registry Service IP: %w", err) + return nil, fmt.Errorf("could not get development container registry Service IP: %w", err) } - dockerRegistrySecret, err := kubernetes.DockerRegistrySecret(ctx, "camel-k", "ck-dev-registry", clusterIP, "admin", "password") + dockerRegistrySecret, err := kubernetes.DockerRegistrySecret(ctx, deployNamespace, "ck-dev-registry", clusterIP, "admin", "password") if err != nil { - return fmt.Errorf("could not generate development container registry push secret: %w", err) + return nil, fmt.Errorf("could not generate development container registry push secret: %w", err) } dockerRegistrySecret.SetOwnerReferences([]metav1.OwnerReference{*ref}) - if err := c.Create(ctx, dockerRegistrySecret); err != nil { - return fmt.Errorf("could not create development container registry push secret: %w", err) + if err := replace(ctx, c, dockerRegistrySecret); err != nil { + return nil, fmt.Errorf("could not create development container registry push secret: %w", err) } + return ®istryConf{ + clusterIP: clusterIP, + insecure: "false", + dockerRegistrySecretName: dockerRegistrySecret.GetName(), + }, nil +} + +// OverrideRegistryConfiguration is in charge to change the registry configuration at runtime. +func OverrideRegistryConfiguration(conf *registryConf) { + if conf == nil { + return + } log.Infof("Setting up development container registry configuration environment variables (registry IP %s). Notice that it overrides the operator configuration"+ - " but it won't override any IntegrationProfile configuration.", clusterIP) - os.Setenv("REGISTRY_ADDRESS", clusterIP) - os.Setenv("REGISTRY_INSECURE", "false") - os.Setenv("REGISTRY_SECRET", dockerRegistrySecret.GetName()) + " but it won't override any IntegrationProfile configuration.", conf.clusterIP) + os.Setenv("REGISTRY_ADDRESS", conf.clusterIP) + os.Setenv("REGISTRY_INSECURE", conf.insecure) + os.Setenv("REGISTRY_SECRET", conf.dockerRegistrySecretName) // We must reinitialize to get those values just changed in the default platform configuration platform.InitPlatform() - - return nil } -func getOwnerRef(ctx context.Context, c client.Client) (*metav1.OwnerReference, error) { - operatorDeploy := &appsv1.Deployment{} +func getOwnerRef(ctx context.Context, c client.Client, operatorName, deployNamespace string) (*metav1.OwnerReference, error) { + var deployments appsv1.DeploymentList - err := c.Get(ctx, types.NamespacedName{ - // TODO: change theme - Name: "camel-k-operator", - Namespace: "camel-k", - }, operatorDeploy) - if err != nil { + if err := c.List(ctx, &deployments, + ctrlcli.InNamespace(deployNamespace), + ctrlcli.MatchingLabels{"name": operatorName}, + ); err != nil { return nil, err } + if len(deployments.Items) != 1 { + return nil, fmt.Errorf("expected exactly one deployment with label name=%s in namespace %s, got %d", + operatorName, deployNamespace, len(deployments.Items)) + } + + operatorDeploy := &deployments.Items[0] + ownerRef := metav1.OwnerReference{ APIVersion: "apps/v1", Kind: "Deployment", @@ -182,7 +208,7 @@ func waitForClusterIP(ctx context.Context, c client.Client, namespace, name stri ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - ticker := time.NewTicker(500 * time.Millisecond) + ticker := time.NewTicker(serviceRegistryClusterIPPollingTime) defer ticker.Stop() for { @@ -213,3 +239,24 @@ func waitForClusterIP(ctx context.Context, c client.Client, namespace, name stri } } } + +func replace(ctx context.Context, c client.Client, obj ctrlcli.Object) error { + copied := obj.DeepCopyObject() + + current, ok := copied.(ctrlcli.Object) + if !ok { + return fmt.Errorf("DeepCopyObject returned %T, expected controller runtime Object", copied) + } + + err := c.Get(ctx, ctrlcli.ObjectKeyFromObject(obj), current) + if apierrors.IsNotFound(err) { + return c.Create(ctx, obj) + } + if err != nil { + return err + } + + obj.SetResourceVersion(current.GetResourceVersion()) + + return c.Update(ctx, obj) +} diff --git a/pkg/install/registry_test.go b/pkg/install/registry_test.go new file mode 100644 index 000000000..4fc39ed31 --- /dev/null +++ b/pkg/install/registry_test.go @@ -0,0 +1,313 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package install + +import ( + "context" + "testing" + "time" + + "github.com/apache/camel-k/v2/pkg/client" + "github.com/apache/camel-k/v2/pkg/internal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func operatorDeployment() *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "camel-k-operator", + Namespace: "camel-k", + UID: types.UID("operator-uid"), + Labels: map[string]string{ + "name": "camel-k-operator", + }, + }, + } +} + +func TestGetOwnerRef(t *testing.T) { + t.Run("returns owner reference", func(t *testing.T) { + fakeClient, err := internal.NewFakeClient(operatorDeployment()) + require.NoError(t, err) + + ref, err := getOwnerRef( + context.Background(), + fakeClient, + "camel-k-operator", + "camel-k", + ) + + require.NoError(t, err) + require.NotNil(t, ref) + + assert.Equal(t, "apps/v1", ref.APIVersion) + assert.Equal(t, "Deployment", ref.Kind) + assert.Equal(t, "camel-k-operator", ref.Name) + assert.Equal(t, types.UID("operator-uid"), ref.UID) + + require.NotNil(t, ref.Controller) + assert.True(t, *ref.Controller) + + require.NotNil(t, ref.BlockOwnerDeletion) + assert.True(t, *ref.BlockOwnerDeletion) + }) + + t.Run("returns error when operator deployment does not exist", func(t *testing.T) { + fakeClient, err := internal.NewFakeClient() + require.NoError(t, err) + + _, err = getOwnerRef( + context.Background(), + fakeClient, + "camel-k-operator", + "camel-k", + ) + + require.Error(t, err) + assert.Contains(t, err.Error(), "expected exactly one deployment") + }) +} + +func TestWaitForClusterIP(t *testing.T) { + t.Run("returns cluster IP", func(t *testing.T) { + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "registry", + Namespace: "camel-k", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "10.96.0.20", + }, + } + + fakeClient, err := internal.NewFakeClient(service) + require.NoError(t, err) + + ip, err := waitForClusterIP( + context.Background(), + fakeClient, + "camel-k", + "registry", + time.Second, + ) + + require.NoError(t, err) + assert.Equal(t, "10.96.0.20", ip) + }) + + t.Run("returns get error", func(t *testing.T) { + fakeClient, err := internal.NewFakeClient() + require.NoError(t, err) + + _, err = waitForClusterIP( + context.Background(), + fakeClient, + "camel-k", + "registry", + time.Second, + ) + + require.Error(t, err) + assert.True(t, apierrors.IsNotFound(err)) + }) + + t.Run("times out when ClusterIP is not assigned", func(t *testing.T) { + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "registry", + Namespace: "camel-k", + }, + } + + fakeClient, err := internal.NewFakeClient(service) + require.NoError(t, err) + + _, err = waitForClusterIP( + context.Background(), + fakeClient, + "camel-k", + "registry", + 10*time.Millisecond, + ) + + require.Error(t, err) + assert.Contains(t, err.Error(), "timed out waiting for Service") + }) + + t.Run("times out for headless service", func(t *testing.T) { + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "registry", + Namespace: "camel-k", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: corev1.ClusterIPNone, + }, + } + + fakeClient, err := internal.NewFakeClient(service) + require.NoError(t, err) + + _, err = waitForClusterIP( + context.Background(), + fakeClient, + "camel-k", + "registry", + 10*time.Millisecond, + ) + + require.Error(t, err) + assert.Contains(t, err.Error(), "timed out waiting for Service") + }) +} + +func TestReplace(t *testing.T) { + ctx := context.Background() + + t.Run("creates object when it does not exist", func(t *testing.T) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "camel-k", + }, + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + } + + fakeClient, err := internal.NewFakeClient() + require.NoError(t, err) + + err = replace(ctx, fakeClient, secret) + require.NoError(t, err) + + var actual corev1.Secret + require.NoError(t, fakeClient.Get(ctx, types.NamespacedName{ + Name: "test-secret", + Namespace: "camel-k", + }, &actual)) + + assert.Equal(t, []byte("bar"), actual.Data["foo"]) + }) + + t.Run("updates existing object", func(t *testing.T) { + existing := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "camel-k", + }, + Data: map[string][]byte{ + "foo": []byte("old"), + }, + } + + fakeClient, err := internal.NewFakeClient(existing) + require.NoError(t, err) + + updated := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "camel-k", + }, + Data: map[string][]byte{ + "foo": []byte("new"), + }, + } + + err = replace(ctx, fakeClient, updated) + require.NoError(t, err) + + var actual corev1.Secret + require.NoError(t, fakeClient.Get(ctx, types.NamespacedName{ + Name: "test-secret", + Namespace: "camel-k", + }, &actual)) + + assert.Equal(t, []byte("new"), actual.Data["foo"]) + }) +} + +func TestOperatorStartupRegistry(t *testing.T) { + t.Setenv("NAMESPACE", "camel-k") + + crtSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-secret", + Namespace: "camel-k", + }, + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + } + + fakeClient, err := internal.NewFakeClient(operatorDeployment(), crtSecret) + require.NoError(t, err) + + // This is running in parallel, simulating the setting of an IP, when the Service is create by the + // OperatorStartupRegistry func. + setServiceClusterIP(context.Background(), fakeClient, "camel-k", "registry", "10.96.0.20") + + conf, err := OperatorStartupRegistry( + context.Background(), + fakeClient, + false, + crtSecret, + ) + + require.NoError(t, err) + require.NotNil(t, conf) + + assert.Equal(t, "10.96.0.20", conf.clusterIP) + assert.Equal(t, "false", conf.insecure) + assert.Equal(t, "ck-dev-registry", conf.dockerRegistrySecretName) +} + +// Useful to simulate Kubernetes IP assignment. +func setServiceClusterIP(ctx context.Context, c client.Client, namespace string, name string, clusterIP string) { + go func() { + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() + + for { + var service corev1.Service + + err := c.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: name, + }, &service) + + if err == nil { + service.Spec.ClusterIP = clusterIP + _ = c.Update(ctx, &service) + return + } + + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } + }() +} diff --git a/pkg/resources/config/rbac/dev-registry-role.yaml b/pkg/resources/config/rbac/dev-registry-role.yaml index deac37f64..2d937c506 100644 --- a/pkg/resources/config/rbac/dev-registry-role.yaml +++ b/pkg/resources/config/rbac/dev-registry-role.yaml @@ -15,6 +15,9 @@ # limitations under the License. # --------------------------------------------------------------------------- +# Required to setup an internal development registry. You can skip this configuration in a production environment. +# Note that the resources names must match the ones expected in the code, which, are static. + kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -28,3 +31,44 @@ rules: - secrets verbs: - create +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - update + resourceNames: + - registry-auth + - registry-tls + - ck-dev-registry +- apiGroups: + - apps + resources: + - deployments + verbs: + - create +- apiGroups: + - apps + resources: + - deployments + verbs: + - get + - update + resourceNames: + - registry +- apiGroups: + - "" + resources: + - services + verbs: + - create +- apiGroups: + - "" + resources: + - services + verbs: + - get + - update + resourceNames: + - registry diff --git a/pkg/util/kubernetes/docker_secret.go b/pkg/util/kubernetes/docker_secret.go index 56b48ff18..4d4d81e49 100644 --- a/pkg/util/kubernetes/docker_secret.go +++ b/pkg/util/kubernetes/docker_secret.go @@ -26,7 +26,6 @@ import ( "os" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type dockerConfigJSON struct { @@ -50,6 +49,7 @@ func buildDockerConfigJSON(server, username, password string) ([]byte, error) { }, }, } + return json.Marshal(cfg) } @@ -61,11 +61,9 @@ func DockerRegistrySecret(ctx context.Context, namespace, name, server, username } secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Type: corev1.SecretTypeDockerConfigJson, + Name: name, + Namespace: namespace, + Type: corev1.SecretTypeDockerConfigJson, Data: map[string][]byte{ corev1.DockerConfigJsonKey: dockerCfgJSON, }, @@ -77,7 +75,7 @@ func DockerRegistrySecret(ctx context.Context, namespace, name, server, username // TLSSecretFromFiles mirrors `kubectl create secret tls --cert=<path> --key=<path>`, // reading the cert/key from disk and validating them the same way kubectl does // (tls.X509KeyPair) before submitting to the API. -func TLSSecretFromFiles(ctx context.Context, namespace, name string, certPath, keyPath string) (*corev1.Secret, error) { +func TLSSecretFromFiles(ctx context.Context, namespace, name string, keyPath, certPath string) (*corev1.Secret, error) { certData, err := os.ReadFile(certPath) if err != nil { return nil, fmt.Errorf("reading cert file %q: %w", certPath, err) @@ -95,11 +93,9 @@ func TLSSecretFromFiles(ctx context.Context, namespace, name string, certPath, k } secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Type: corev1.SecretTypeTLS, + Name: name, + Namespace: namespace, + Type: corev1.SecretTypeTLS, Data: map[string][]byte{ corev1.TLSCertKey: certData, corev1.TLSPrivateKeyKey: keyData,
