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 06366afacb4a02ce7ec2fb3e2816740469fc54e2
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Sat Sep 19 12:50:27 2026 +0200

    feat(install): support development container registry
    
    Closes #6758
---
 pkg/cmd/operator/operator.go                       |  20 ++
 pkg/install/registry.go                            | 215 +++++++++++++++++++++
 .../config/manager/operator-deployment.yaml        |   3 +
 ...ization.yaml => dev-registry-role-binding.yaml} |  26 +--
 .../{kustomization.yaml => dev-registry-role.yaml} |  26 +--
 pkg/resources/config/rbac/kustomization.yaml       |   2 +
 pkg/resources/resources/registry/deploy.yaml       |  81 ++++++++
 .../registry/secret.yaml}                          |  21 +-
 .../registry/service.yaml}                         |  29 +--
 pkg/util/kubernetes/docker_secret.go               | 110 +++++++++++
 10 files changed, 481 insertions(+), 52 deletions(-)

diff --git a/pkg/cmd/operator/operator.go b/pkg/cmd/operator/operator.go
index 2d34ea496..60b181bf8 100644
--- a/pkg/cmd/operator/operator.go
+++ b/pkg/cmd/operator/operator.go
@@ -239,6 +239,26 @@ func Run(healthPort, monitoringPort int32, leaderElection 
bool, leaderElectionID
        defer installCancel()
        install.OperatorStartupOptionalTools(installCtx, bootstrapClient, log)
 
+       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")
+               }
+       }
+
        log.Info("Starting the manager")
        exitOnError(mgr.Start(ctx), "manager exited non-zero")
 }
diff --git a/pkg/install/registry.go b/pkg/install/registry.go
new file mode 100644
index 000000000..460b6a740
--- /dev/null
+++ b/pkg/install/registry.go
@@ -0,0 +1,215 @@
+/*
+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"
+       "fmt"
+       "os"
+       "os/exec"
+       "time"
+
+       "github.com/apache/camel-k/v2/pkg/client"
+       "github.com/apache/camel-k/v2/pkg/platform"
+       "github.com/apache/camel-k/v2/pkg/resources"
+       "github.com/apache/camel-k/v2/pkg/util/kubernetes"
+       "github.com/apache/camel-k/v2/pkg/util/log"
+       appsv1 "k8s.io/api/apps/v1"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/types"
+       "sigs.k8s.io/yaml"
+)
+
+// OperatorStartupRegistry tries to install optional development container 
registry.
+func OperatorStartupRegistry(ctx context.Context, c client.Client, withSecret 
bool) 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)
+       }
+       var registrySecret corev1.Secret
+       if err = yaml.Unmarshal(secret, &registrySecret); err != nil {
+               return 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)
+       }
+       var registryService corev1.Service
+       if err = yaml.Unmarshal(service, &registryService); err != nil {
+               return 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)
+       }
+       var registryDeploy appsv1.Deployment
+       if err = yaml.Unmarshal(deploy, &registryDeploy); err != nil {
+               return fmt.Errorf("could not parse development container 
registry Deployment configuration: %w", err)
+       }
+
+       if withSecret && len(registryDeploy.Spec.Template.Spec.Containers) > 0 {
+               registryDeploy.Spec.Template.Spec.Containers[0].Env =
+                       
append(registryDeploy.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
+                               Name:  "REGISTRY_AUTH",
+                               Value: "htpasswd",
+                       })
+               registryDeploy.Spec.Template.Spec.Containers[0].Env =
+                       
append(registryDeploy.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
+                               Name:  "REGISTRY_AUTH_HTPASSWD_REALM",
+                               Value: "Registry Realm",
+                       })
+               registryDeploy.Spec.Template.Spec.Containers[0].Env =
+                       
append(registryDeploy.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
+                               Name:  "REGISTRY_AUTH_HTPASSWD_PATH",
+                               Value: "/auth/htpasswd",
+                       })
+       }
+
+       // Get owner reference to operator deployment to manage garbage 
collection
+       ref, err := getOwnerRef(ctx, c)
+       if err != nil {
+               return 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)
+       }
+
+       registrySecret.SetNamespace("camel-k")
+       registryService.SetNamespace("camel-k")
+       registryDeploy.SetNamespace("camel-k")
+       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, &registrySecret); err != nil {
+               return fmt.Errorf("could not create development container 
registry Secret configuration: %w", err)
+       }
+       if err := c.Create(ctx, &registryService); err != nil {
+               return fmt.Errorf("could not create development container 
registry Service configuration: %w", err)
+       }
+       if err := c.Create(ctx, &registryDeploy); err != nil {
+               return 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)
+       if err != nil {
+               return 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")
+       if err != nil {
+               return 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)
+       }
+
+       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())
+       // 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{}
+
+       err := c.Get(ctx, types.NamespacedName{
+               // TODO: change theme
+               Name:      "camel-k-operator",
+               Namespace: "camel-k",
+       }, operatorDeploy)
+       if err != nil {
+               return nil, err
+       }
+
+       ownerRef := metav1.OwnerReference{
+               APIVersion:         "apps/v1",
+               Kind:               "Deployment",
+               Name:               operatorDeploy.Name,
+               UID:                operatorDeploy.UID,
+               Controller:         new(true),
+               BlockOwnerDeletion: new(true),
+       }
+
+       return &ownerRef, nil
+}
+
+func waitForClusterIP(ctx context.Context, c client.Client, namespace, name 
string, timeout time.Duration) (string, error) {
+       ctx, cancel := context.WithTimeout(ctx, timeout)
+       defer cancel()
+
+       ticker := time.NewTicker(500 * time.Millisecond)
+       defer ticker.Stop()
+
+       for {
+               var svc corev1.Service
+
+               err := c.Get(ctx, types.NamespacedName{
+                       Namespace: namespace,
+                       Name:      name,
+               }, &svc)
+               if err != nil {
+                       return "", err
+               }
+
+               if svc.Spec.ClusterIP != "" &&
+                       svc.Spec.ClusterIP != corev1.ClusterIPNone {
+                       return svc.Spec.ClusterIP, nil
+               }
+
+               select {
+               case <-ctx.Done():
+                       return "", fmt.Errorf(
+                               "timed out waiting for Service %s/%s to get a 
ClusterIP: %w",
+                               namespace,
+                               name,
+                               ctx.Err(),
+                       )
+               case <-ticker.C:
+               }
+       }
+}
diff --git a/pkg/resources/config/manager/operator-deployment.yaml 
b/pkg/resources/config/manager/operator-deployment.yaml
index 771d21d53..548b84999 100644
--- a/pkg/resources/config/manager/operator-deployment.yaml
+++ b/pkg/resources/config/manager/operator-deployment.yaml
@@ -81,6 +81,9 @@ spec:
             # 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"
           # Attempt to read bootstrap configuration from configmap or secret
           envFrom:
             - configMapRef:
diff --git a/pkg/resources/config/rbac/kustomization.yaml 
b/pkg/resources/config/rbac/dev-registry-role-binding.yaml
similarity index 74%
copy from pkg/resources/config/rbac/kustomization.yaml
copy to pkg/resources/config/rbac/dev-registry-role-binding.yaml
index d6a983f2c..61a32dc31 100644
--- a/pkg/resources/config/rbac/kustomization.yaml
+++ b/pkg/resources/config/rbac/dev-registry-role-binding.yaml
@@ -15,16 +15,16 @@
 # limitations under the License.
 # ---------------------------------------------------------------------------
 
-#
-# rbac resources applicable for all kubernetes platforms
-#
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-resources:
-- builder-role.yaml
-- builder-role-openshift.yaml
-- kamelets-viewer-role.yaml
-- builder-role-binding.yaml
-- builder-role-binding-openshift.yaml
-- kamelets-viewer-role-binding.yaml
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: camel-k-dev-registry
+  labels:
+    app: "camel-k"
+subjects:
+- kind: ServiceAccount
+  name: camel-k-operator
+roleRef:
+  kind: Role
+  name: camel-k-dev-registry
+  apiGroup: rbac.authorization.k8s.io
diff --git a/pkg/resources/config/rbac/kustomization.yaml 
b/pkg/resources/config/rbac/dev-registry-role.yaml
similarity index 74%
copy from pkg/resources/config/rbac/kustomization.yaml
copy to pkg/resources/config/rbac/dev-registry-role.yaml
index d6a983f2c..deac37f64 100644
--- a/pkg/resources/config/rbac/kustomization.yaml
+++ b/pkg/resources/config/rbac/dev-registry-role.yaml
@@ -15,16 +15,16 @@
 # limitations under the License.
 # ---------------------------------------------------------------------------
 
-#
-# rbac resources applicable for all kubernetes platforms
-#
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-resources:
-- builder-role.yaml
-- builder-role-openshift.yaml
-- kamelets-viewer-role.yaml
-- builder-role-binding.yaml
-- builder-role-binding-openshift.yaml
-- kamelets-viewer-role-binding.yaml
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: camel-k-dev-registry
+  labels:
+    app: "camel-k"
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - create
diff --git a/pkg/resources/config/rbac/kustomization.yaml 
b/pkg/resources/config/rbac/kustomization.yaml
index d6a983f2c..629776651 100644
--- a/pkg/resources/config/rbac/kustomization.yaml
+++ b/pkg/resources/config/rbac/kustomization.yaml
@@ -24,7 +24,9 @@ kind: Kustomization
 resources:
 - builder-role.yaml
 - builder-role-openshift.yaml
+- dev-registry-role.yaml
 - kamelets-viewer-role.yaml
 - builder-role-binding.yaml
 - builder-role-binding-openshift.yaml
+- dev-registry-role-binding.yaml
 - kamelets-viewer-role-binding.yaml
diff --git a/pkg/resources/resources/registry/deploy.yaml 
b/pkg/resources/resources/registry/deploy.yaml
new file mode 100644
index 000000000..51d8e61d1
--- /dev/null
+++ b/pkg/resources/resources/registry/deploy.yaml
@@ -0,0 +1,81 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: registry
+spec:
+  selector:
+    matchLabels:
+      app: registry
+  template:
+    metadata:
+      labels:
+        app: registry
+    spec:
+      containers:
+        - name: registry
+          image: registry:3
+          env:
+            - name: REGISTRY_HTTP_ADDR
+              value: 0.0.0.0:443
+            - name: REGISTRY_HTTP_TLS_CERTIFICATE
+              value: /certs/tls.crt
+            - name: REGISTRY_HTTP_TLS_KEY
+              value: /certs/tls.key
+            - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
+              value: /registry-data
+            # Required to include a secret credential
+            # The operator will be in charge to turn them on if
+            # configured
+            # - name: REGISTRY_AUTH
+            #   value: htpasswd
+            # - name: REGISTRY_AUTH_HTPASSWD_REALM
+            #   value: Registry Realm
+            # - name: REGISTRY_AUTH_HTPASSWD_PATH
+            #   value: /auth/htpasswd
+          ports:
+            - containerPort: 5000
+              name: registry
+            - containerPort: 443
+              name: https
+          volumeMounts:
+            - name: registry-data
+              mountPath: /registry-data
+            - name: registry-auth
+              mountPath: /auth
+              readOnly: true
+            - name: registry-tls
+              mountPath: /certs
+              readOnly: true
+          resources:
+            requests:
+              cpu: 100m
+              memory: 128Mi
+            limits:
+              cpu: 500m
+              memory: 512Mi
+      volumes:
+        - name: registry-data
+          emptyDir: {}
+        - name: registry-auth
+          secret:
+            secretName: registry-auth
+        - name: registry-tls
+          secret:
+            secretName: registry-tls
diff --git a/pkg/resources/config/rbac/kustomization.yaml 
b/pkg/resources/resources/registry/secret.yaml
similarity index 74%
copy from pkg/resources/config/rbac/kustomization.yaml
copy to pkg/resources/resources/registry/secret.yaml
index d6a983f2c..e2c414b18 100644
--- a/pkg/resources/config/rbac/kustomization.yaml
+++ b/pkg/resources/resources/registry/secret.yaml
@@ -15,16 +15,11 @@
 # limitations under the License.
 # ---------------------------------------------------------------------------
 
-#
-# rbac resources applicable for all kubernetes platforms
-#
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-resources:
-- builder-role.yaml
-- builder-role-openshift.yaml
-- kamelets-viewer-role.yaml
-- builder-role-binding.yaml
-- builder-role-binding-openshift.yaml
-- kamelets-viewer-role-binding.yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: registry-auth
+type: Opaque
+stringData:
+  htpasswd: |
+    admin:$2y$05$b8N7UpPbxfFRppWxO5bEXOrKdryg/DUPpQ9xXpKPsGKikeAMzrSHe
diff --git a/pkg/resources/config/rbac/kustomization.yaml 
b/pkg/resources/resources/registry/service.yaml
similarity index 74%
copy from pkg/resources/config/rbac/kustomization.yaml
copy to pkg/resources/resources/registry/service.yaml
index d6a983f2c..a28da8131 100644
--- a/pkg/resources/config/rbac/kustomization.yaml
+++ b/pkg/resources/resources/registry/service.yaml
@@ -15,16 +15,19 @@
 # limitations under the License.
 # ---------------------------------------------------------------------------
 
-#
-# rbac resources applicable for all kubernetes platforms
-#
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-resources:
-- builder-role.yaml
-- builder-role-openshift.yaml
-- kamelets-viewer-role.yaml
-- builder-role-binding.yaml
-- builder-role-binding-openshift.yaml
-- kamelets-viewer-role-binding.yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: registry
+spec:
+  selector:
+    app: registry
+  ports:
+  - name: http
+    port: 80
+    protocol: TCP
+    targetPort: 5000
+  - name: https
+    port: 443
+    protocol: TCP
+    targetPort: 443
diff --git a/pkg/util/kubernetes/docker_secret.go 
b/pkg/util/kubernetes/docker_secret.go
new file mode 100644
index 000000000..56b48ff18
--- /dev/null
+++ b/pkg/util/kubernetes/docker_secret.go
@@ -0,0 +1,110 @@
+/*
+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 kubernetes
+
+import (
+       "context"
+       "crypto/tls"
+       "encoding/base64"
+       "encoding/json"
+       "fmt"
+       "os"
+
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type dockerConfigJSON struct {
+       Auths map[string]dockerConfigEntry `json:"auths"`
+}
+
+type dockerConfigEntry struct {
+       Username string `json:"username"`
+       Password string `json:"password"`
+       Auth     string `json:"auth"`
+}
+
+func buildDockerConfigJSON(server, username, password string) ([]byte, error) {
+       auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + 
password))
+       cfg := dockerConfigJSON{
+               Auths: map[string]dockerConfigEntry{
+                       server: {
+                               Username: username,
+                               Password: password,
+                               Auth:     auth,
+                       },
+               },
+       }
+       return json.Marshal(cfg)
+}
+
+// DockerRegistrySecret returns a kubernetes.io/dockerconfigjson secret.
+func DockerRegistrySecret(ctx context.Context, namespace, name, server, 
username, password string) (*corev1.Secret, error) {
+       dockerCfgJSON, err := buildDockerConfigJSON(server, username, password)
+       if err != nil {
+               return nil, fmt.Errorf("building docker config json: %w", err)
+       }
+
+       secret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      name,
+                       Namespace: namespace,
+               },
+               Type: corev1.SecretTypeDockerConfigJson,
+               Data: map[string][]byte{
+                       corev1.DockerConfigJsonKey: dockerCfgJSON,
+               },
+       }
+
+       return secret, nil
+}
+
+// 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) {
+       certData, err := os.ReadFile(certPath)
+       if err != nil {
+               return nil, fmt.Errorf("reading cert file %q: %w", certPath, 
err)
+       }
+
+       keyData, err := os.ReadFile(keyPath)
+       if err != nil {
+               return nil, fmt.Errorf("reading key file %q: %w", keyPath, err)
+       }
+
+       // Same validation kubectl performs: parses PEM blocks and confirms the
+       // private key matches the certificate's public key.
+       if _, err := tls.X509KeyPair(certData, keyData); err != nil {
+               return nil, fmt.Errorf("failed to load key pair: %w", err)
+       }
+
+       secret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      name,
+                       Namespace: namespace,
+               },
+               Type: corev1.SecretTypeTLS,
+               Data: map[string][]byte{
+                       corev1.TLSCertKey:       certData,
+                       corev1.TLSPrivateKeyKey: keyData,
+               },
+       }
+
+       return secret, nil
+}

Reply via email to