diff --git a/api/crds/manifests/gateway.openmcp.cloud_gatewayserviceconfigs.yaml b/api/crds/manifests/gateway.openmcp.cloud_gatewayserviceconfigs.yaml index 392f123..7d26a37 100644 --- a/api/crds/manifests/gateway.openmcp.cloud_gatewayserviceconfigs.yaml +++ b/api/crds/manifests/gateway.openmcp.cloud_gatewayserviceconfigs.yaml @@ -93,6 +93,21 @@ spec: chart: description: Chart configuration for Envoy Gateway. properties: + secretRef: + description: |- + SecretRef specifies the Secret containing authentication credentials + for the OCIRepository. + For HTTP/S basic auth the secret must contain 'username' and 'password' + fields. + Support for TLS auth using the 'certFile' and 'keyFile', and/or 'caFile' + keys is deprecated. Please use `.spec.certSecretRef` instead. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object tag: description: 'Tag of the chart. Example: 1.5.4' minLength: 1 @@ -112,6 +127,21 @@ spec: gateway: description: 'EnvoyGateway image. Example: docker.io/envoyproxy/gateway:v1.5.1' type: string + imagePullSecrets: + description: |- + ImagePullSecrets specifies the Secrets containing authentication credentials + for the Envoy Gateway deployment. + items: + description: LocalObjectReference contains enough information + to locate the referenced Kubernetes resource object. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: array proxy: description: 'EnvoyProxy image. Example: docker.io/envoyproxy/envoy:distroless-v1.35.3' type: string diff --git a/api/gateway/v1alpha1/config_types.go b/api/gateway/v1alpha1/config_types.go index 55ac484..ff15321 100644 --- a/api/gateway/v1alpha1/config_types.go +++ b/api/gateway/v1alpha1/config_types.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "github.com/fluxcd/pkg/apis/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -60,6 +61,15 @@ type EnvoyGatewayChart struct { // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength=1 Tag string `json:"tag"` + + // SecretRef specifies the Secret containing authentication credentials + // for the OCIRepository. + // For HTTP/S basic auth the secret must contain 'username' and 'password' + // fields. + // Support for TLS auth using the 'certFile' and 'keyFile', and/or 'caFile' + // keys is deprecated. Please use `.spec.certSecretRef` instead. + // +optional + SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` } type ImagesConfig struct { @@ -71,6 +81,11 @@ type ImagesConfig struct { // Ratelimit image. Example: docker.io/envoyproxy/ratelimit:e74a664a Ratelimit string `json:"rateLimit"` + + // ImagePullSecrets specifies the Secrets containing authentication credentials + // for the Envoy Gateway deployment. + // +optional + ImagePullSecrets []meta.LocalObjectReference `json:"imagePullSecrets,omitempty"` } type DNSConfig struct { diff --git a/api/gateway/v1alpha1/zz_generated.deepcopy.go b/api/gateway/v1alpha1/zz_generated.deepcopy.go index 1fd0f04..e430c23 100644 --- a/api/gateway/v1alpha1/zz_generated.deepcopy.go +++ b/api/gateway/v1alpha1/zz_generated.deepcopy.go @@ -5,6 +5,7 @@ package v1alpha1 import ( + "github.com/fluxcd/pkg/apis/meta" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -88,6 +89,11 @@ func (in *DNSConfig) DeepCopy() *DNSConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvoyGatewayChart) DeepCopyInto(out *EnvoyGatewayChart) { *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(meta.LocalObjectReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyGatewayChart. @@ -106,9 +112,9 @@ func (in *EnvoyGatewayConfig) DeepCopyInto(out *EnvoyGatewayConfig) { if in.Images != nil { in, out := &in.Images, &out.Images *out = new(ImagesConfig) - **out = **in + (*in).DeepCopyInto(*out) } - out.Chart = in.Chart + in.Chart.DeepCopyInto(&out.Chart) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyGatewayConfig. @@ -206,6 +212,11 @@ func (in *GatewayServiceConfigSpec) DeepCopy() *GatewayServiceConfigSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImagesConfig) DeepCopyInto(out *ImagesConfig) { *out = *in + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]meta.LocalObjectReference, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagesConfig. diff --git a/api/go.mod b/api/go.mod index 5855d41..43b9514 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,6 +3,7 @@ module github.com/openmcp-project/platform-service-gateway/api go 1.25.3 require ( + github.com/fluxcd/pkg/apis/meta v1.22.0 github.com/openmcp-project/controller-utils v0.23.3 github.com/openmcp-project/openmcp-operator/api v0.16.0 k8s.io/apiextensions-apiserver v0.34.1 diff --git a/api/go.sum b/api/go.sum index 54ef712..92f200c 100644 --- a/api/go.sum +++ b/api/go.sum @@ -14,6 +14,8 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fluxcd/pkg/apis/meta v1.22.0 h1:EHWQH5ZWml7i8eZ/AMjm1jxid3j/PQ31p+hIwCt6crM= +github.com/fluxcd/pkg/apis/meta v1.22.0/go.mod h1:Kc1+bWe5p0doROzuV9XiTfV/oL3ddsemYXt8ZYWdVVg= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= diff --git a/internal/controllers/cluster/controller.go b/internal/controllers/cluster/controller.go index 8fe6c61..c236fb0 100644 --- a/internal/controllers/cluster/controller.go +++ b/internal/controllers/cluster/controller.go @@ -231,7 +231,6 @@ func (r *ClusterReconciler) buildGatewayManager(ctx context.Context, req reconci DNSConfig: r.Config.Spec.DNS, PlatformClient: r.PlatformCluster.Client(), ClusterClient: access.Client(), - PullSecrets: []corev1.LocalObjectReference{}, // TODO FluxKubeconfig: &fluxmeta.KubeConfigReference{ SecretRef: &fluxmeta.SecretKeyReference{ Name: ar.Status.SecretRef.Name, diff --git a/pkg/envoy/config.go b/pkg/envoy/config.go index eef4479..7f1b5f6 100644 --- a/pkg/envoy/config.go +++ b/pkg/envoy/config.go @@ -6,7 +6,6 @@ import ( "fmt" "time" - "github.com/openmcp-project/platform-service-gateway/pkg/utils" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,6 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + + "github.com/openmcp-project/platform-service-gateway/pkg/utils" ) var ( @@ -163,7 +164,7 @@ func (g *Gateway) reconcileEnvoyProxyFunc(obj *unstructured.Unstructured) func() "envoyDeployment": map[string]any{ "container": container, "pod": map[string]any{ - "imagePullSecrets": g.PullSecrets, + "imagePullSecrets": g.EnvoyConfig.Images.ImagePullSecrets, }, }, }, diff --git a/pkg/envoy/deployment.go b/pkg/envoy/deployment.go index b9dbc97..66f661c 100644 --- a/pkg/envoy/deployment.go +++ b/pkg/envoy/deployment.go @@ -33,7 +33,6 @@ type Gateway struct { DNSConfig v1alpha1.DNSConfig PlatformClient client.Client ClusterClient client.Client - PullSecrets []corev1.LocalObjectReference FluxKubeconfig *fluxmeta.KubeConfigReference } @@ -41,17 +40,22 @@ func (g *Gateway) InstallOrUpdate(ctx context.Context) error { repo := g.getRepo() helmRelease := g.getHelmRelease() + imagePullSecretOps := g.ensureSecrets(ctx, deploymentNamespace) + ops := []applyOperation{ ensureNamespace(deploymentNamespace, g.ClusterClient), - { + } + ops = append(ops, imagePullSecretOps...) + ops = append(ops, + applyOperation{ obj: repo, f: g.reconcileOCIRepositoryFunc(repo), }, - { + applyOperation{ obj: helmRelease, f: g.reconcileHelmReleaseFunc(repo.Name, helmRelease), }, - } + ) return createOrUpdate(ctx, g.PlatformClient, ops...) } @@ -93,11 +97,7 @@ func (g *Gateway) reconcileOCIRepositoryFunc(obj *sourcev1.OCIRepository) func() Tag: g.EnvoyConfig.Chart.Tag, } - if len(g.PullSecrets) > 0 { - obj.Spec.SecretRef = &fluxmeta.LocalObjectReference{ - Name: g.PullSecrets[0].Name, - } - } + obj.Spec.SecretRef = g.EnvoyConfig.Chart.SecretRef return nil } @@ -136,6 +136,46 @@ func (g *Gateway) reconcileHelmReleaseFunc(repoName string, obj *helmv2.HelmRele } } +func (g *Gateway) reconcileSecretFunc(ctx context.Context, obj *corev1.Secret) func() error { + return func() error { + sourceSecret := &corev1.Secret{} + sourceKey := client.ObjectKey{ + Namespace: g.Cluster.Namespace, + Name: obj.Name, + } + if err := g.PlatformClient.Get(ctx, sourceKey, sourceSecret); err != nil { + return fmt.Errorf("failed to get secret %s: %w", sourceKey, err) + } + + obj.Data = sourceSecret.Data + obj.Type = sourceSecret.Type + return nil + } +} + +func (g *Gateway) ensureSecrets(ctx context.Context, targetNamespace string) []applyOperation { + if g.EnvoyConfig.Images == nil || len(g.EnvoyConfig.Images.ImagePullSecrets) == 0 { + return nil + } + + ops := make([]applyOperation, len(g.EnvoyConfig.Images.ImagePullSecrets)) + for i, imagePullSecret := range g.EnvoyConfig.Images.ImagePullSecrets { + obj := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: imagePullSecret.Name, + Namespace: targetNamespace, + }, + } + ops[i] = applyOperation{ + obj: obj, + f: g.reconcileSecretFunc(ctx, obj), + c: g.ClusterClient, + } + } + + return ops +} + func (g *Gateway) generateHelmValuesJSON() (*apiextensionsv1.JSON, error) { values := g.generateHelmValues() raw, err := json.Marshal(values) @@ -160,7 +200,7 @@ func (g *Gateway) generateHelmValues() map[string]any { return map[string]any{ "global": map[string]any{ "images": images, - "imagePullSecrets": g.PullSecrets, + "imagePullSecrets": g.EnvoyConfig.Images.ImagePullSecrets, }, } }