From 1b31e29f2e61cb7c9d9cf3ec8bdaadcfd1a31a37 Mon Sep 17 00:00:00 2001 From: Fabian Burth Date: Wed, 15 Jan 2025 10:04:54 +0100 Subject: [PATCH] merge secretrefs and configrefs into single list (#81) #### What this PR does / why we need it This PR merges config ref and secret ref into a single list and exchanges the implicit defaulting mechanism (implicitly inheriting the configuration of the previous object in the pipeline - e.g. component inherits from repository) with an explicit mechanism. **Merging into single list** The order in which the configuration is applied is relevant. This becomes confusing and cumbersome with two separate lists and additional mechanisms would be needed. **Changing the defaulting mechanism** 1) The previous defaulting mechanism would automatically inherit the referenced config maps if the config maps were empty and the secrets if the secrets are empty. This does not make too much sense (but was overseen, since originally, this was already intended as a single list). If a user would provide all his configuration data in secrets and therefore, doesn't specify any config maps, the mechanism would still have inherited the config maps and potentially overwritten some of the configs provided in the secret. 2) The new mechanism allows to only inherit parts of the configuration. Thus, inherit the reference to config map a BUT NOT the reference to config map b. This is useful, because the e.g. the Replication needs the same credentials as the Component (since it needs to have access to the component), but it does not need other parts of the configuration such as the signing keys. #### Which issue(s) this PR fixes #80 --- .golangci.yaml | 3 + api/v1alpha1/common_types.go | 27 + api/v1alpha1/component_types.go | 78 +-- api/v1alpha1/interfaces.go | 62 +-- api/v1alpha1/ocmrepository_types.go | 102 +--- api/v1alpha1/replication_types.go | 78 +-- api/v1alpha1/resource_types.go | 73 +-- api/v1alpha1/zz_generated.deepcopy.go | 125 ++--- .../delivery.ocm.software_components.yaml | 190 ++++--- ...delivery.ocm.software_ocmrepositories.yaml | 185 +++---- .../delivery.ocm.software_replications.yaml | 167 +++--- .../delivery.ocm.software_resources.yaml | 173 +++--- ...mponents.delivery.ocm.software_sample.yaml | 11 +- ...ication.delivery.ocm.software_sample.yaml} | 5 +- ...pository.delivery.ocm.software_sample.yaml | 8 +- ...resource.delivery.ocm.software_sample.yaml | 21 +- .../component/component_controller.go | 27 +- .../component/component_controller_test.go | 408 +++++++++++++- .../controller/ocmrepository/controller.go | 20 +- .../ocmrepository/controller_test.go | 136 ++++- internal/controller/replication/controller.go | 60 +- .../controller/replication/controller_test.go | 9 +- .../resource/resource_controller.go | 19 +- pkg/ocm/configure.go | 52 -- pkg/ocm/lookup.go | 119 ---- pkg/ocm/lookup_test.go | 155 ------ pkg/ocm/ocm.go | 275 +++++---- pkg/ocm/ocm_test.go | 522 ++++++++++++++++-- pkg/ocm/verification.go | 41 ++ .../with-config/Component-intermediate.yaml | 6 + .../with-config/Component-origin.yaml | 6 + .../OCMRepository-intermediate.yaml | 6 +- .../with-config/OCMRepository-target.yaml | 6 +- .../Replication-to-intermediate.yaml | 13 +- .../with-config/Replication-to-target.yaml | 13 +- 35 files changed, 1832 insertions(+), 1369 deletions(-) rename config/samples/{replication.delivery.ocm.software_sample.yaml.yaml => replication.delivery.ocm.software_sample.yaml} (80%) delete mode 100644 pkg/ocm/configure.go delete mode 100644 pkg/ocm/lookup.go delete mode 100644 pkg/ocm/lookup_test.go diff --git a/.golangci.yaml b/.golangci.yaml index 39388f94..3ada0e9b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -52,6 +52,9 @@ issues: - source: "https://" linters: - lll + - source: "^// \\+kubebuilder:*" + linters: + - lll - path: pkg/defaults/ linters: - lll diff --git a/api/v1alpha1/common_types.go b/api/v1alpha1/common_types.go index a18b7f95..138ab694 100644 --- a/api/v1alpha1/common_types.go +++ b/api/v1alpha1/common_types.go @@ -17,11 +17,38 @@ limitations under the License. package v1alpha1 import ( + "github.com/fluxcd/pkg/apis/meta" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ocmv1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) +type ConfigurationPolicy string + +const ( + ConfigurationPolicyPropagate ConfigurationPolicy = "Propagate" + ConfigurationPolicyDoNotPropagate ConfigurationPolicy = "DoNotPropagate" +) + +// OCMConfiguration defines a configuration applied to the reconciliation of an +// ocm k8s object as well as the policy for its propagation of this +// configuration. +// +kubebuilder:validation:XValidation:rule="((!has(self.apiVersion) || self.apiVersion == \"\" || self.apiVersion == \"v1\") && (self.kind == \"Secret\" || self.kind == \"ConfigMap\")) || (self.apiVersion == \"delivery.ocm.software/v1alpha1\" && (self.kind == \"OCMRepository\" || self.kind == \"Component\" || self.kind == \"Resource\" || self.kind == \"Replication\"))",message="apiVersion must be one of \"v1\" with kind \"Secret\" or \"ConfigMap\" or \"delivery.ocm.software/v1alpha1\" with the kind of an OCM kubernetes object" +type OCMConfiguration struct { + // Ref reference config maps or secrets containing arbitrary + // ocm config data (in the ocm config file format), or other configurable + // ocm api objects (OCMRepository, Component, Resource) to + // reuse their propagated configuration. + meta.NamespacedObjectKindReference `json:",inline"` + // Policy affects the propagation behavior of the configuration. If set to + // ConfigurationPolicyPropagate other ocm api objects can reference this + // object to reuse this configuration. + // +kubebuilder:validation:Enum:="Propagate";"DoNotPropagate" + // +kubebuilder:default:="DoNotPropagate" + // +required + Policy ConfigurationPolicy `json:"policy,omitempty"` +} + type ObjectKey struct { // +optional Namespace string `json:"namespace,omitempty"` diff --git a/api/v1alpha1/component_types.go b/api/v1alpha1/component_types.go index 8d2ba428..c005248a 100644 --- a/api/v1alpha1/component_types.go +++ b/api/v1alpha1/component_types.go @@ -32,6 +32,8 @@ var ( DowngradePolicyEnforce DowngradePolicy = "Enforce" ) +const KindComponent = "Component" + // ComponentSpec defines the desired state of Component. type ComponentSpec struct { // RepositoryRef is a reference to a OCMRepository. @@ -55,6 +57,7 @@ type ComponentSpec struct { // +kubebuilder:default:=Deny // +optional DowngradePolicy DowngradePolicy `json:"downgradePolicy,omitempty"` + // Semver defines the constraint of the fetched version. '>=v0.1'. // +required Semver string `json:"semver"` @@ -70,18 +73,10 @@ type ComponentSpec struct { // +optional Verify []Verification `json:"verify,omitempty"` + // OCMConfig defines references to secrets, config maps or ocm api + // objects providing configuration data including credentials. // +optional - SecretRefs []corev1.LocalObjectReference `json:"secretRefs,omitempty"` - - // +optional - ConfigRefs []corev1.LocalObjectReference `json:"configRefs,omitempty"` - - // The secrets and configs referred to by SecretRef (or SecretRefs) and Config (or ConfigRefs) may contain ocm - // config data. The ocm config allows to specify sets of configuration data - // (s. https://ocm.software/docs/cli-reference/help/configfile/). If the SecretRef (or SecretRefs) and ConfigRef and - // ConfigRefs contain ocm config sets, the user may specify which config set he wants to be effective. - // +optional - ConfigSet *string `json:"configSet"` + OCMConfig []OCMConfiguration `json:"ocmConfig,omitempty"` // Interval at which the repository will be checked for new component // versions. @@ -105,10 +100,9 @@ type ComponentStatus struct { // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // The component controller generates an artifact which is a list of - // component descriptors. If the components were verified, other controllers - // (e.g. Resource controller) can use this without having to verify the - // signature again. + // ArtifactRef references the generated artifact containing a list of + // component descriptors. This list can be used by other controllers to + // avoid re-downloading (and potentially also re-verifying) the components. // +optional ArtifactRef corev1.LocalObjectReference `json:"artifactRef,omitempty"` @@ -117,32 +111,12 @@ type ComponentStatus struct { // reconciliation. // +optional Component ComponentInfo `json:"component,omitempty"` - // Propagate its effective secrets. Other controllers (e.g. Resource - // controller) may use this as default if they do not explicitly refer a - // secret. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - SecretRefs []corev1.LocalObjectReference `json:"secretRefs,omitempty"` - - // Propagate its effective configs. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // refer a config. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - ConfigRefs []corev1.LocalObjectReference `json:"configRefs,omitempty"` - - // Propagate its effective config set. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // specify a config set. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. + + // EffectiveOCMConfig specifies the entirety of config maps and secrets + // whose configuration data was applied to the Component reconciliation, + // in the order the configuration data was applied. // +optional - ConfigSet string `json:"configSet,omitempty"` + EffectiveOCMConfig []OCMConfiguration `json:"effectiveOCMConfig,omitempty"` } // +kubebuilder:object:root=true @@ -194,28 +168,12 @@ func (in Component) GetRequeueAfter() time.Duration { return in.Spec.Interval.Duration } -func (in *Component) GetSecretRefs() []corev1.LocalObjectReference { - return in.Spec.SecretRefs -} - -func (in *Component) GetEffectiveSecretRefs() []corev1.LocalObjectReference { - return in.Status.SecretRefs -} - -func (in *Component) GetConfigRefs() []corev1.LocalObjectReference { - return in.Spec.ConfigRefs -} - -func (in *Component) GetEffectiveConfigRefs() []corev1.LocalObjectReference { - return in.Status.ConfigRefs -} - -func (in *Component) GetConfigSet() *string { - return in.Spec.ConfigSet +func (in *Component) GetSpecifiedOCMConfig() []OCMConfiguration { + return in.Spec.OCMConfig } -func (in *Component) GetEffectiveConfigSet() string { - return in.Status.ConfigSet +func (in *Component) GetEffectiveOCMConfig() []OCMConfiguration { + return in.Status.EffectiveOCMConfig } func (in *Component) GetVerifications() []Verification { diff --git a/api/v1alpha1/interfaces.go b/api/v1alpha1/interfaces.go index dd0a915d..6b3deb41 100644 --- a/api/v1alpha1/interfaces.go +++ b/api/v1alpha1/interfaces.go @@ -2,58 +2,28 @@ package v1alpha1 import ( "github.com/fluxcd/pkg/runtime/conditions" - corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) -// SecretRefProvider are objects that provide secret refs. The interface allows all implementers to use the same -// function to retrieve its secrets. -// -// GetEffectiveSecretRefs returns references to the secrets that were effectively available for that object. -// For example, the ComponentSpec's secret ref and secret refs might be empty, but the OCMRepositorySpec of the -// OCMRepository the Component references, might have a secret ref or secret refs specified. If this is the case -// (so the ComponentSpec does not specify any secret refs but the OCMRepositorySpec does), the Component inherits -// (or defaults to) these secrets. -// For OCMRepository, GetSecretRefs() and GetEffectiveSecretRefs() would then return the same thing. For Component, -// GetSecretRefs() would return an empty list while GetEffectiveSecretRefs() would return the same thing as the -// OCMRepository's GetSecretRefs() and GetEffectiveSecretRefs(). -// Each SecretRefProvider exposes its effective secrets in its status (see e.g. OCMRepository.Status). This way, -// controllers such as the Resource controller does not have to backtrack the entire kubernetes object chain to -// OCMRepository to read its defaults. -// +kubebuilder:object:generate=false -type SecretRefProvider interface { - client.Object - - // GetSecretRefs return the list of all secret references specified in the spec of the implementing object. - GetSecretRefs() []corev1.LocalObjectReference - - // GetEffectiveSecretRefs returns the list of all secret references specified in the spec of the implementing object. - GetEffectiveSecretRefs() []corev1.LocalObjectReference -} - -// ConfigRefProvider are objects that provide secret refs. The interface allows all implementers to use the same -// function to retrieve its secrets. -// -// For a detailed explanation, see SecretRefProvider. +// ConfigRefProvider are objects that provide configurations such as credentials +// or other ocm configuration. The interface allows all implementers to use the +// same function to retrieve its configuration. // +kubebuilder:object:generate=false type ConfigRefProvider interface { client.Object - GetConfigRefs() []corev1.LocalObjectReference - GetEffectiveConfigRefs() []corev1.LocalObjectReference -} -// ConfigSetProvider are objects that may contain config sets. The interface allows all implementers to use the same -// function to retrieve its config set. -// -// GetConfigSet() returns a string pointer because we have to distinguish between a purposefully set empty value and a -// unset value to determine whether to use the default. -// -// For a detailed explanation, see SecretRefProvider. -// +kubebuilder:object:generate=false -type ConfigSetProvider interface { - client.Object - GetConfigSet() *string - GetEffectiveConfigSet() string + // GetSpecifiedOCMConfig returns the configurations specifically specified + // in the spec of the ocm k8s object. + // CAREFUL: The configurations retrieved from this method might reference + // other configurable OCM objects (OCMRepository, Component, Resource, + // Replication). In that case the EffectiveOCMConfig (referencing Secrets or + // ConfigMaps) propagated by the referenced ocm k8s objects have to be + // resolved (see ocm.GetEffectiveConfig). + GetSpecifiedOCMConfig() []OCMConfiguration + + // GetEffectiveOCMConfig returns the effective configurations propagated by + // the ocm k8s object. + GetEffectiveOCMConfig() []OCMConfiguration } // OCMK8SObject is a composite interface that the ocm-k8s-toolkit resources implement which allows them to use @@ -61,9 +31,7 @@ type ConfigSetProvider interface { // +kubebuilder:object:generate=false type OCMK8SObject interface { conditions.Setter - SecretRefProvider ConfigRefProvider - ConfigSetProvider } // VerificationProvider are objects that may provide verification information. The interface allows all implementers to diff --git a/api/v1alpha1/ocmrepository_types.go b/api/v1alpha1/ocmrepository_types.go index d11aa492..58b7c2ab 100644 --- a/api/v1alpha1/ocmrepository_types.go +++ b/api/v1alpha1/ocmrepository_types.go @@ -18,15 +18,14 @@ package v1alpha1 import ( "fmt" - "slices" - "strings" "time" - v1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const KindOCMRepository = "OCMRepository" + // OCMRepositorySpec defines the desired state of OCMRepository. type OCMRepositorySpec struct { // RepositorySpec is the config of an ocm repository containing component @@ -36,26 +35,10 @@ type OCMRepositorySpec struct { // +required RepositorySpec *apiextensionsv1.JSON `json:"repositorySpec"` - // SecretRefs are references to one or multiple secrets that contain - // credentials or ocm configurations - // (https://github.com/open-component-model/ocm/blob/main/docs/reference/ocm_configfile.md). - // +optional - SecretRefs []v1.LocalObjectReference `json:"secretRefs,omitempty"` - - // ConfigRefs are references to one or multiple config maps that contain - // ocm configurations - // (https://github.com/open-component-model/ocm/blob/main/docs/reference/ocm_configfile.md). - // +optional - ConfigRefs []v1.LocalObjectReference `json:"configRefs,omitempty"` - - // The secrets and configs referred to by SecretRef (or SecretRefs) and - // Config (or ConfigRefs) may contain ocm config data. The ocm config - // allows to specify sets of configuration data - // (s. https://ocm.software/docs/cli-reference/help/configfile/). If the - // SecretRef (or SecretRefs) and ConfigRef and ConfigRefs contain ocm config - // sets, the user may specify which config set he wants to be effective. + // OCMConfig defines references to secrets, config maps or ocm api + // objects providing configuration data including credentials. // +optional - ConfigSet *string `json:"configSet"` + OCMConfig []OCMConfiguration `json:"ocmConfig,omitempty"` // Interval at which the ocm repository specified by the RepositorySpec // validated. @@ -79,32 +62,11 @@ type OCMRepositoryStatus struct { // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // Propagate its effective secrets. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // refer a secret. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. + // EffectiveOCMConfig specifies the entirety of config maps and secrets + // whose configuration data was applied to the OCMRepository reconciliation, + // in the order the configuration data was applied. // +optional - SecretRefs []v1.LocalObjectReference `json:"secretRefs,omitempty"` - - // Propagate its effective configs. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // refer a config. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - ConfigRefs []v1.LocalObjectReference `json:"configRefs,omitempty"` - - // Propagate its effective config set. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // specify a config set. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - ConfigSet string `json:"configSet,omitempty"` + EffectiveOCMConfig []OCMConfiguration `json:"effectiveOCMConfig,omitempty"` } // GetConditions returns the conditions of the OCMRepository. @@ -123,6 +85,14 @@ func (in OCMRepository) GetRequeueAfter() time.Duration { return in.Spec.Interval.Duration } +func (in *OCMRepository) GetSpecifiedOCMConfig() []OCMConfiguration { + return in.Spec.OCMConfig +} + +func (in *OCMRepository) GetEffectiveOCMConfig() []OCMConfiguration { + return in.Status.EffectiveOCMConfig +} + // GetVID unique identifier of the object. func (in *OCMRepository) GetVID() map[string]string { vid := fmt.Sprintf("%s:%s", in.Namespace, in.Name) @@ -136,44 +106,6 @@ func (in *OCMRepository) SetObservedGeneration(v int64) { in.Status.ObservedGeneration = v } -func (in *OCMRepository) GetSecretRefs() []v1.LocalObjectReference { - return in.Spec.SecretRefs -} - -func (in *OCMRepository) SetEffectiveSecretRefs() { - in.Status.SecretRefs = slices.Clone(in.GetSecretRefs()) -} - -func (in *OCMRepository) GetEffectiveSecretRefs() []v1.LocalObjectReference { - return in.Status.SecretRefs -} - -func (in *OCMRepository) GetConfigRefs() []v1.LocalObjectReference { - return in.Spec.ConfigRefs -} - -func (in *OCMRepository) SetEffectiveConfigRefs() { - in.Status.ConfigRefs = slices.Clone(in.GetConfigRefs()) -} - -func (in *OCMRepository) GetEffectiveConfigRefs() []v1.LocalObjectReference { - return in.Status.ConfigRefs -} - -func (in *OCMRepository) GetConfigSet() *string { - return in.Spec.ConfigSet -} - -func (in *OCMRepository) SetEffectiveConfigSet() { - if in.Spec.ConfigSet != nil { - in.Status.ConfigSet = strings.Clone(*in.Spec.ConfigSet) - } -} - -func (in *OCMRepository) GetEffectiveConfigSet() string { - return in.Status.ConfigSet -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status diff --git a/api/v1alpha1/replication_types.go b/api/v1alpha1/replication_types.go index e3ab6587..66c784ed 100644 --- a/api/v1alpha1/replication_types.go +++ b/api/v1alpha1/replication_types.go @@ -20,7 +20,6 @@ import ( "fmt" "time" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -28,6 +27,8 @@ import ( // (exposing a subset of its options in the manifest). // This allows transferring components into a private registry based on a "ocmops" based process. +const KindReplication = "Replication" + // ReplicationSpec defines the desired state of Replication. type ReplicationSpec struct { // ComponentRef is a reference to a Component to be replicated. @@ -59,18 +60,10 @@ type ReplicationSpec struct { // +optional Verify []Verification `json:"verify,omitempty"` + // OCMConfig defines references to secrets, config maps or ocm api + // objects providing configuration data including credentials. // +optional - SecretRefs []corev1.LocalObjectReference `json:"secretRefs,omitempty"` - - // +optional - ConfigRefs []corev1.LocalObjectReference `json:"configRefs,omitempty"` - - // The secrets and configs referred to by SecretRef (or SecretRefs) and Config (or ConfigRefs) may contain ocm - // config data. The ocm config allows to specify sets of configuration data - // (s. https://ocm.software/docs/cli-reference/help/configfile/). If the SecretRef (or SecretRefs) and ConfigRef and - // ConfigRefs contain ocm config sets, the user may specify which config set he wants to be effective. - // +optional - ConfigSet *string `json:"configSet"` + OCMConfig []OCMConfiguration `json:"ocmConfig,omitempty"` } // ReplicationStatus defines the observed state of Replication. @@ -88,32 +81,11 @@ type ReplicationStatus struct { // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // Propagate its effective secrets. Other controllers (e.g. Resource - // controller) may use this as default if they do not explicitly refer a - // secret. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - SecretRefs []corev1.LocalObjectReference `json:"secretRefs,omitempty"` - - // Propagate its effective configs. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // refer a config. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. + // EffectiveOCMConfig specifies the entirety of config maps and secrets + // whose configuration data was applied to the Resource reconciliation, + // in the order the configuration data was applied. // +optional - ConfigRefs []corev1.LocalObjectReference `json:"configRefs,omitempty"` - - // Propagate its effective config set. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // specify a config set. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - ConfigSet string `json:"configSet,omitempty"` + EffectiveOCMConfig []OCMConfiguration `json:"effectiveOCMConfig,omitempty"` } // TransferStatus holds the status of a single 'ocm transfer' run. @@ -167,6 +139,14 @@ func (repl Replication) GetRequeueAfter() time.Duration { return repl.Spec.Interval.Duration } +func (repl *Replication) GetSpecifiedOCMConfig() []OCMConfiguration { + return repl.Spec.OCMConfig +} + +func (repl *Replication) GetEffectiveOCMConfig() []OCMConfiguration { + return repl.Status.EffectiveOCMConfig +} + // GetVID unique identifier of the object. func (repl *Replication) GetVID() map[string]string { vid := fmt.Sprintf("%s:%s", repl.Namespace, repl.Name) @@ -226,30 +206,6 @@ func (repl *Replication) IsInHistory(component, version, targetSpec string) bool return false } -func (repl *Replication) GetSecretRefs() []corev1.LocalObjectReference { - return repl.Spec.SecretRefs -} - -func (repl *Replication) GetEffectiveSecretRefs() []corev1.LocalObjectReference { - return repl.Status.SecretRefs -} - -func (repl *Replication) GetConfigRefs() []corev1.LocalObjectReference { - return repl.Spec.ConfigRefs -} - -func (repl *Replication) GetEffectiveConfigRefs() []corev1.LocalObjectReference { - return repl.Status.ConfigRefs -} - -func (repl *Replication) GetConfigSet() *string { - return repl.Spec.ConfigSet -} - -func (repl *Replication) GetEffectiveConfigSet() string { - return repl.Status.ConfigSet -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status diff --git a/api/v1alpha1/resource_types.go b/api/v1alpha1/resource_types.go index fa4ec36e..b8ec88a1 100644 --- a/api/v1alpha1/resource_types.go +++ b/api/v1alpha1/resource_types.go @@ -20,7 +20,7 @@ import ( "fmt" "time" - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -30,24 +30,16 @@ const KindResource = "Resource" type ResourceSpec struct { // ComponentRef is a reference to a Component. // +required - ComponentRef v1.LocalObjectReference `json:"componentRef"` + ComponentRef corev1.LocalObjectReference `json:"componentRef"` // Resource identifies the ocm resource to be fetched. // +required Resource ResourceID `json:"resource"` + // OCMConfig defines references to secrets, config maps or ocm api + // objects providing configuration data including credentials. // +optional - SecretRefs []v1.LocalObjectReference `json:"secretRefs,omitempty"` - - // +optional - ConfigRefs []v1.LocalObjectReference `json:"configRefs,omitempty"` - - // The secrets and configs referred to by SecretRef (or SecretRefs) and Config (or ConfigRefs) may contain ocm - // config data. The ocm config allows to specify sets of configuration data - // (s. https://ocm.software/docs/cli-reference/help/configfile/). If the SecretRef (or SecretRefs) and ConfigRef and - // ConfigRefs contain ocm config sets, the user may specify which config set he wants to be effective. - // +optional - ConfigSet *string `json:"configSet,omitempty"` + OCMConfig []OCMConfiguration `json:"ocmConfig,omitempty"` // Interval at which the resource is checked for updates. // +required @@ -73,37 +65,16 @@ type ResourceStatus struct { // ArtifactRef points to the Artifact which represents the output of the // last successful Resource sync. // +optional - ArtifactRef v1.LocalObjectReference `json:"artifactRef,omitempty"` + ArtifactRef corev1.LocalObjectReference `json:"artifactRef,omitempty"` // +optional Resource *ResourceInfo `json:"resource,omitempty"` - // Propagate its effective secrets. Other controllers (e.g. Resource - // controller) may use this as default if they do not explicitly refer a - // secret. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. + // EffectiveOCMConfig specifies the entirety of config maps and secrets + // whose configuration data was applied to the Resource reconciliation, + // in the order the configuration data was applied. // +optional - SecretRefs []v1.LocalObjectReference `json:"secretRefs,omitempty"` - - // Propagate its effective configs. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // refer a config. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - ConfigRefs []v1.LocalObjectReference `json:"configRefs,omitempty"` - - // Propagate its effective config set. Other controllers (e.g. Component or - // Resource controller) may use this as default if they do not explicitly - // specify a config set. - // This is required to allow transitive defaulting (thus, e.g. Component - // defaults from OCMRepository and Resource defaults from Component) without - // having to traverse the entire chain. - // +optional - ConfigSet string `json:"configSet,omitempty"` + EffectiveOCMConfig []OCMConfiguration `json:"effectiveOCMConfig,omitempty"` } // +kubebuilder:object:root=true @@ -152,28 +123,12 @@ func (in Resource) GetRequeueAfter() time.Duration { return in.Spec.Interval.Duration } -func (in *Resource) GetSecretRefs() []v1.LocalObjectReference { - return in.Spec.SecretRefs -} - -func (in *Resource) GetEffectiveSecretRefs() []v1.LocalObjectReference { - return in.Status.SecretRefs -} - -func (in *Resource) GetConfigRefs() []v1.LocalObjectReference { - return in.Spec.ConfigRefs -} - -func (in *Resource) GetEffectiveConfigRefs() []v1.LocalObjectReference { - return in.Status.ConfigRefs -} - -func (in *Resource) GetConfigSet() *string { - return in.Spec.ConfigSet +func (in *Resource) GetSpecifiedOCMConfig() []OCMConfiguration { + return in.Spec.OCMConfig } -func (in *Resource) GetEffectiveConfigSet() string { - return in.Status.ConfigSet +func (in *Resource) GetEffectiveOCMConfig() []OCMConfiguration { + return in.Status.EffectiveOCMConfig } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 30875246..d7b6a68d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5,7 +5,6 @@ package v1alpha1 import ( - corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -100,21 +99,11 @@ func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { *out = make([]Verification, len(*in)) copy(*out, *in) } - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.OCMConfig != nil { + in, out := &in.OCMConfig, &out.OCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigSet != nil { - in, out := &in.ConfigSet, &out.ConfigSet - *out = new(string) - **out = **in - } out.Interval = in.Interval } @@ -140,14 +129,9 @@ func (in *ComponentStatus) DeepCopyInto(out *ComponentStatus) { } out.ArtifactRef = in.ArtifactRef in.Component.DeepCopyInto(&out.Component) - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.EffectiveOCMConfig != nil { + in, out := &in.EffectiveOCMConfig, &out.EffectiveOCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } } @@ -753,6 +737,22 @@ func (in *LocalizedResourceStatus) DeepCopy() *LocalizedResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OCMConfiguration) DeepCopyInto(out *OCMConfiguration) { + *out = *in + out.NamespacedObjectKindReference = in.NamespacedObjectKindReference +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCMConfiguration. +func (in *OCMConfiguration) DeepCopy() *OCMConfiguration { + if in == nil { + return nil + } + out := new(OCMConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OCMRepository) DeepCopyInto(out *OCMRepository) { *out = *in @@ -820,21 +820,11 @@ func (in *OCMRepositorySpec) DeepCopyInto(out *OCMRepositorySpec) { *out = new(apiextensionsv1.JSON) (*in).DeepCopyInto(*out) } - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.OCMConfig != nil { + in, out := &in.OCMConfig, &out.OCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigSet != nil { - in, out := &in.ConfigSet, &out.ConfigSet - *out = new(string) - **out = **in - } out.Interval = in.Interval } @@ -858,14 +848,9 @@ func (in *OCMRepositoryStatus) DeepCopyInto(out *OCMRepositoryStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.EffectiveOCMConfig != nil { + in, out := &in.EffectiveOCMConfig, &out.EffectiveOCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } } @@ -965,21 +950,11 @@ func (in *ReplicationSpec) DeepCopyInto(out *ReplicationSpec) { *out = make([]Verification, len(*in)) copy(*out, *in) } - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.OCMConfig != nil { + in, out := &in.OCMConfig, &out.OCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } - if in.ConfigSet != nil { - in, out := &in.ConfigSet, &out.ConfigSet - *out = new(string) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationSpec. @@ -1009,14 +984,9 @@ func (in *ReplicationStatus) DeepCopyInto(out *ReplicationStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.EffectiveOCMConfig != nil { + in, out := &in.EffectiveOCMConfig, &out.EffectiveOCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } } @@ -1249,21 +1219,11 @@ func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) { *out = *in out.ComponentRef = in.ComponentRef in.Resource.DeepCopyInto(&out.Resource) - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.OCMConfig != nil { + in, out := &in.OCMConfig, &out.OCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } - if in.ConfigSet != nil { - in, out := &in.ConfigSet, &out.ConfigSet - *out = new(string) - **out = **in - } out.Interval = in.Interval } @@ -1293,14 +1253,9 @@ func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) { *out = new(ResourceInfo) (*in).DeepCopyInto(*out) } - if in.SecretRefs != nil { - in, out := &in.SecretRefs, &out.SecretRefs - *out = make([]corev1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.ConfigRefs != nil { - in, out := &in.ConfigRefs, &out.ConfigRefs - *out = make([]corev1.LocalObjectReference, len(*in)) + if in.EffectiveOCMConfig != nil { + in, out := &in.EffectiveOCMConfig, &out.EffectiveOCMConfig + *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } } diff --git a/config/crd/bases/delivery.ocm.software_components.yaml b/config/crd/bases/delivery.ocm.software_components.yaml index ca648c4f..5325f24f 100644 --- a/config/crd/bases/delivery.ocm.software_components.yaml +++ b/config/crd/bases/delivery.ocm.software_components.yaml @@ -42,31 +42,6 @@ spec: component: description: Component is the name of the ocm component. type: string - configRefs: - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array - configSet: - description: |- - The secrets and configs referred to by SecretRef (or SecretRefs) and Config (or ConfigRefs) may contain ocm - config data. The ocm config allows to specify sets of configuration data - (s. https://ocm.software/docs/cli-reference/help/configfile/). If the SecretRef (or SecretRefs) and ConfigRef and - ConfigRefs contain ocm config sets, the user may specify which config set he wants to be effective. - type: string downgradePolicy: default: Deny description: |- @@ -89,6 +64,55 @@ spec: Interval at which the repository will be checked for new component versions. type: string + ocmConfig: + description: |- + OCMConfig defines references to secrets, config maps or ocm api + objects providing configuration data including credentials. + items: + description: |- + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. + properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate + description: |- + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate + type: string + required: + - kind + - name + - policy + type: object + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) + type: array repositoryRef: description: RepositoryRef is a reference to a OCMRepository. properties: @@ -99,24 +123,6 @@ spec: required: - name type: object - secretRefs: - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array semver: description: Semver defines the constraint of the fetched version. '>=v0.1'. @@ -176,10 +182,9 @@ spec: properties: artifactRef: description: |- - The component controller generates an artifact which is a list of - component descriptors. If the components were verified, other controllers - (e.g. Resource controller) can use this without having to verify the - signature again. + ArtifactRef references the generated artifact containing a list of + component descriptors. This list can be used by other controllers to + avoid re-downloading (and potentially also re-verifying) the components. properties: name: default: "" @@ -266,71 +271,62 @@ spec: - type type: object type: array - configRefs: + effectiveOCMConfig: description: |- - Propagate its effective configs. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - refer a config. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. + EffectiveOCMConfig specifies the entirety of config maps and secrets + whose configuration data was applied to the Component reconciliation, + in the order the configuration data was applied. items: description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string name: - default: "" + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate type: string + required: + - kind + - name + - policy type: object - x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) type: array - configSet: - description: |- - Propagate its effective config set. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - specify a config set. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - type: string observedGeneration: description: |- ObservedGeneration is the last observed generation of the ComponentStatus object. format: int64 type: integer - secretRefs: - description: |- - Propagate its effective secrets. Other controllers (e.g. Resource - controller) may use this as default if they do not explicitly refer a - secret. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array type: object required: - spec diff --git a/config/crd/bases/delivery.ocm.software_ocmrepositories.yaml b/config/crd/bases/delivery.ocm.software_ocmrepositories.yaml index 23894d03..e4d926f3 100644 --- a/config/crd/bases/delivery.ocm.software_ocmrepositories.yaml +++ b/config/crd/bases/delivery.ocm.software_ocmrepositories.yaml @@ -39,42 +39,60 @@ spec: spec: description: OCMRepositorySpec defines the desired state of OCMRepository. properties: - configRefs: + interval: + description: |- + Interval at which the ocm repository specified by the RepositorySpec + validated. + type: string + ocmConfig: description: |- - ConfigRefs are references to one or multiple config maps that contain - ocm configurations - (https://github.com/open-component-model/ocm/blob/main/docs/reference/ocm_configfile.md). + OCMConfig defines references to secrets, config maps or ocm api + objects providing configuration data including credentials. items: description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string name: - default: "" + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate type: string + required: + - kind + - name + - policy type: object - x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) type: array - configSet: - description: |- - The secrets and configs referred to by SecretRef (or SecretRefs) and - Config (or ConfigRefs) may contain ocm config data. The ocm config - allows to specify sets of configuration data - (s. https://ocm.software/docs/cli-reference/help/configfile/). If the - SecretRef (or SecretRefs) and ConfigRef and ConfigRefs contain ocm config - sets, the user may specify which config set he wants to be effective. - type: string - interval: - description: |- - Interval at which the ocm repository specified by the RepositorySpec - validated. - type: string repositorySpec: description: |- RepositorySpec is the config of an ocm repository containing component @@ -82,28 +100,6 @@ spec: specification https://github.com/open-component-model/ocm-spec/blob/main/doc/04-extensions/03-storage-backends/README.md. x-kubernetes-preserve-unknown-fields: true - secretRefs: - description: |- - SecretRefs are references to one or multiple secrets that contain - credentials or ocm configurations - (https://github.com/open-component-model/ocm/blob/main/docs/reference/ocm_configfile.md). - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array suspend: description: |- Suspend tells the controller to suspend the reconciliation of this @@ -173,71 +169,62 @@ spec: - type type: object type: array - configRefs: + effectiveOCMConfig: description: |- - Propagate its effective configs. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - refer a config. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. + EffectiveOCMConfig specifies the entirety of config maps and secrets + whose configuration data was applied to the OCMRepository reconciliation, + in the order the configuration data was applied. items: description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string name: - default: "" + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate type: string + required: + - kind + - name + - policy type: object - x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) type: array - configSet: - description: |- - Propagate its effective config set. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - specify a config set. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - type: string observedGeneration: description: |- ObservedGeneration is the last observed generation of the OCMRepository object. format: int64 type: integer - secretRefs: - description: |- - Propagate its effective secrets. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - refer a secret. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array type: object required: - spec diff --git a/config/crd/bases/delivery.ocm.software_replications.yaml b/config/crd/bases/delivery.ocm.software_replications.yaml index dcdd4f6d..3c326b19 100644 --- a/config/crd/bases/delivery.ocm.software_replications.yaml +++ b/config/crd/bases/delivery.ocm.software_replications.yaml @@ -49,31 +49,6 @@ spec: required: - name type: object - configRefs: - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array - configSet: - description: |- - The secrets and configs referred to by SecretRef (or SecretRefs) and Config (or ConfigRefs) may contain ocm - config data. The ocm config allows to specify sets of configuration data - (s. https://ocm.software/docs/cli-reference/help/configfile/). If the SecretRef (or SecretRefs) and ConfigRef and - ConfigRefs contain ocm config sets, the user may specify which config set he wants to be effective. - type: string historyLength: default: 10 description: HistoryCapacity is the maximum number of last replication @@ -82,23 +57,54 @@ spec: interval: description: Interval at which the replication is reconciled. type: string - secretRefs: + ocmConfig: + description: |- + OCMConfig defines references to secrets, config maps or ocm api + objects providing configuration data including credentials. items: description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string name: - default: "" + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate type: string + required: + - kind + - name + - policy type: object - x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) type: array suspend: description: |- @@ -216,40 +222,56 @@ spec: - type type: object type: array - configRefs: + effectiveOCMConfig: description: |- - Propagate its effective configs. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - refer a config. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. + EffectiveOCMConfig specifies the entirety of config maps and secrets + whose configuration data was applied to the Resource reconciliation, + in the order the configuration data was applied. items: description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string name: - default: "" + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate type: string + required: + - kind + - name + - policy type: object - x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) type: array - configSet: - description: |- - Propagate its effective config set. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - specify a config set. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - type: string history: description: History holds the history of individual 'ocm transfer' runs. @@ -306,31 +328,6 @@ spec: object. format: int64 type: integer - secretRefs: - description: |- - Propagate its effective secrets. Other controllers (e.g. Resource - controller) may use this as default if they do not explicitly refer a - secret. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array type: object type: object served: true diff --git a/config/crd/bases/delivery.ocm.software_resources.yaml b/config/crd/bases/delivery.ocm.software_resources.yaml index 2a38905a..9bb536e1 100644 --- a/config/crd/bases/delivery.ocm.software_resources.yaml +++ b/config/crd/bases/delivery.ocm.software_resources.yaml @@ -53,34 +53,58 @@ spec: type: string type: object x-kubernetes-map-type: atomic - configRefs: + interval: + description: Interval at which the resource is checked for updates. + type: string + ocmConfig: + description: |- + OCMConfig defines references to secrets, config maps or ocm api + objects providing configuration data including credentials. items: description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string name: - default: "" + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate type: string + required: + - kind + - name + - policy type: object - x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) type: array - configSet: - description: |- - The secrets and configs referred to by SecretRef (or SecretRefs) and Config (or ConfigRefs) may contain ocm - config data. The ocm config allows to specify sets of configuration data - (s. https://ocm.software/docs/cli-reference/help/configfile/). If the SecretRef (or SecretRefs) and ConfigRef and - ConfigRefs contain ocm config sets, the user may specify which config set he wants to be effective. - type: string - interval: - description: Interval at which the resource is checked for updates. - type: string resource: description: Resource identifies the ocm resource to be fetched. properties: @@ -112,24 +136,6 @@ spec: required: - byReference type: object - secretRefs: - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array suspend: description: |- Suspend tells the controller to suspend the reconciliation of this @@ -216,40 +222,56 @@ spec: - type type: object type: array - configRefs: + effectiveOCMConfig: description: |- - Propagate its effective configs. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - refer a config. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. + EffectiveOCMConfig specifies the entirety of config maps and secrets + whose configuration data was applied to the Resource reconciliation, + in the order the configuration data was applied. items: description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. + OCMConfiguration defines a configuration applied to the reconciliation of an + ocm k8s object as well as the policy for its propagation of this + configuration. properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string name: - default: "" + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + policy: + default: DoNotPropagate description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Policy affects the propagation behavior of the configuration. If set to + ConfigurationPolicyPropagate other ocm api objects can reference this + object to reuse this configuration. + enum: + - Propagate + - DoNotPropagate type: string + required: + - kind + - name + - policy type: object - x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: apiVersion must be one of "v1" with kind "Secret" or + "ConfigMap" or "delivery.ocm.software/v1alpha1" with the kind + of an OCM kubernetes object + rule: ((!has(self.apiVersion) || self.apiVersion == "" || self.apiVersion + == "v1") && (self.kind == "Secret" || self.kind == "ConfigMap")) + || (self.apiVersion == "delivery.ocm.software/v1alpha1" && (self.kind + == "OCMRepository" || self.kind == "Component" || self.kind + == "Resource" || self.kind == "Replication")) type: array - configSet: - description: |- - Propagate its effective config set. Other controllers (e.g. Component or - Resource controller) may use this as default if they do not explicitly - specify a config set. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - type: string observedGeneration: description: |- ObservedGeneration is the last observed generation of the Resource @@ -278,31 +300,6 @@ spec: - name - type type: object - secretRefs: - description: |- - Propagate its effective secrets. Other controllers (e.g. Resource - controller) may use this as default if they do not explicitly refer a - secret. - This is required to allow transitive defaulting (thus, e.g. Component - defaults from OCMRepository and Resource defaults from Component) without - having to traverse the entire chain. - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array type: object required: - spec diff --git a/config/samples/components.delivery.ocm.software_sample.yaml b/config/samples/components.delivery.ocm.software_sample.yaml index f53d454b..911eec82 100644 --- a/config/samples/components.delivery.ocm.software_sample.yaml +++ b/config/samples/components.delivery.ocm.software_sample.yaml @@ -8,17 +8,16 @@ spec: namespace: default name: open-component-model-repo component: ocm.software/ocmcli - enforceDowngradability: false + downgradePolicy: Deny semver: ">= 6.1.x-0" semverFilter: ".*-rc.*" verify: - signature: ocm secretRef: name: ocm-pk-secret - secretRefs: - - name: ocm-secret - configRefs: - - name: ocm-config - configSet: standard + ocmConfig: + - apiVersion: delivery.ocm.software/v1alpha1 + kind: Replication + name: repo interval: 10m suspend: false diff --git a/config/samples/replication.delivery.ocm.software_sample.yaml.yaml b/config/samples/replication.delivery.ocm.software_sample.yaml similarity index 80% rename from config/samples/replication.delivery.ocm.software_sample.yaml.yaml rename to config/samples/replication.delivery.ocm.software_sample.yaml index fe06bfe2..0ac9497f 100644 --- a/config/samples/replication.delivery.ocm.software_sample.yaml.yaml +++ b/config/samples/replication.delivery.ocm.software_sample.yaml @@ -7,8 +7,9 @@ spec: componentRef: name: ocm-comp namespace: default - configRefs: - - name: transfer-options + ocmConfig: + - kind: ConfigMap + name: transfer-options interval: 10min targetRepositoryRef: name: ocm-repo diff --git a/config/samples/repository.delivery.ocm.software_sample.yaml b/config/samples/repository.delivery.ocm.software_sample.yaml index a0ac98cb..da1f60e9 100644 --- a/config/samples/repository.delivery.ocm.software_sample.yaml +++ b/config/samples/repository.delivery.ocm.software_sample.yaml @@ -7,10 +7,8 @@ spec: repositorySpec: baseUrl: ghcr.io/open-component-model type: OCIRegistry - secretRefs: - - name: ocm-secret - configRefs: - - name: ocm-config - configSet: standard + ocmConfig: + - kind: Secret + name: secret interval: 10m suspend: false diff --git a/config/samples/resource.delivery.ocm.software_sample.yaml b/config/samples/resource.delivery.ocm.software_sample.yaml index 8389ce97..dcf8665d 100644 --- a/config/samples/resource.delivery.ocm.software_sample.yaml +++ b/config/samples/resource.delivery.ocm.software_sample.yaml @@ -5,17 +5,16 @@ metadata: name: ocm-resource spec: componentRef: - namespace: default name: ocm-comp - resourceSelector: - resource: - name: oci - referencePath: - - name: ocm-comp-ref - secretRefs: - - name: ocm-secret - configRefs: - - name: ocm-config - configSet: standard + resource: + byReference: + resource: + name: oci + referencePath: + - name: ocm-comp-ref + ocmConfig: + - apiVersion: delivery.ocm.software/v1alpha1 + kind: Component + name: ocm-comp interval: 10min suspend: false diff --git a/internal/controller/component/component_controller.go b/internal/controller/component/component_controller.go index aec5510c..6f30a323 100644 --- a/internal/controller/component/component_controller.go +++ b/internal/controller/component/component_controller.go @@ -22,7 +22,6 @@ import ( "fmt" "os" "path/filepath" - "slices" "strings" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -175,9 +174,21 @@ func (r *Reconciler) reconcileComponent(ctx context.Context, octx ocmctx.Context // automatically close the session when the ocm context is closed in the above defer octx.Finalizer().Close(session) - err := ocm.ConfigureOCMContext(ctx, r, octx, component, repository) + configs, err := ocm.GetEffectiveConfig(ctx, r.GetClient(), component) if err != nil { - status.MarkNotReady(r.EventRecorder, component, v1alpha1.ConfigureContextFailedReason, "Configuring Context failed") + status.MarkNotReady(r.GetEventRecorder(), component, v1alpha1.ConfigureContextFailedReason, err.Error()) + + return ctrl.Result{}, nil + } + verifications, err := ocm.GetVerifications(ctx, r.GetClient(), component) + if err != nil { + status.MarkNotReady(r.GetEventRecorder(), component, v1alpha1.ConfigureContextFailedReason, err.Error()) + + return ctrl.Result{}, nil + } + err = ocm.ConfigureContext(ctx, octx, r.GetClient(), configs, verifications) + if err != nil { + status.MarkNotReady(r.GetEventRecorder(), component, v1alpha1.ConfigureContextFailedReason, err.Error()) return ctrl.Result{}, err } @@ -242,7 +253,7 @@ func (r *Reconciler) reconcileComponent(ctx context.Context, octx ocmctx.Context } // Update status - r.setComponentStatus(component, v1alpha1.ComponentInfo{ + r.setComponentStatus(component, configs, v1alpha1.ComponentInfo{ RepositorySpec: repository.Spec.RepositorySpec, Component: component.Spec.Component, Version: version, @@ -408,14 +419,10 @@ func (r *Reconciler) normalizeComponentVersionName(name string) string { func (r *Reconciler) setComponentStatus( component *v1alpha1.Component, + configs []v1alpha1.OCMConfiguration, info v1alpha1.ComponentInfo, ) { component.Status.Component = info - component.Status.ConfigRefs = slices.Clone(component.Spec.ConfigRefs) - component.Status.SecretRefs = slices.Clone(component.Spec.SecretRefs) - - if component.Spec.ConfigSet != nil { - component.Status.ConfigSet = *component.Spec.ConfigSet - } + component.Status.EffectiveOCMConfig = configs } diff --git a/internal/controller/component/component_controller_test.go b/internal/controller/component/component_controller_test.go index 623c0746..69913855 100644 --- a/internal/controller/component/component_controller_test.go +++ b/internal/controller/component/component_controller_test.go @@ -33,6 +33,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" artifactv1 "github.com/openfluxcd/artifact/api/v1alpha1" + corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -64,6 +65,10 @@ var _ = Describe("Component Controller", func() { cancel context.CancelFunc env *Builder ctfpath string + + repositoryName string + testNumber int + repositoryObj *v1alpha1.OCMRepository ) BeforeEach(func() { ctfpath = Must(os.MkdirTemp("", CTFPath)) @@ -78,11 +83,6 @@ var _ = Describe("Component Controller", func() { }) Context("component controller", func() { - var ( - repositoryName string - testNumber int - repositoryObj *v1alpha1.OCMRepository - ) BeforeEach(func() { By("creating a repository with name") env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { @@ -392,4 +392,402 @@ var _ = Describe("Component Controller", func() { }).WithTimeout(15 * time.Second).Should(BeTrue()) }) }) + + Context("ocm config handling", func() { + const ( + Namespace = "test-namespace" + ) + + var ( + configs []*corev1.ConfigMap + secrets []*corev1.Secret + ) + + BeforeEach(func() { + By("creating a repository with name") + env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { + env.Component(Component, func() { + env.Version(Version1) + }) + }) + + spec := Must(ctf.NewRepositorySpec(ctf.ACC_READONLY, ctfpath)) + specdata := Must(spec.MarshalJSON()) + + configs, secrets = createTestConfigsAndSecrets(ctx) + + repositoryName = fmt.Sprintf("%s-%d", RepositoryObj, testNumber) + repositoryObj = &v1alpha1.OCMRepository{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: repositoryName, + }, + Spec: v1alpha1.OCMRepositorySpec{ + RepositorySpec: &apiextensionsv1.JSON{ + Raw: specdata, + }, + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[0].Name, + Namespace: secrets[0].Namespace, + }, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[1].Name, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "Secret", + Name: secrets[2].Name, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[0].Name, + Namespace: configs[1].Namespace, + }, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[1].Name, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "ConfigMap", + Name: configs[2].Name, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + }, + Interval: metav1.Duration{Duration: time.Minute * 10}, + }, + } + + Expect(k8sClient.Create(ctx, repositoryObj)).To(Succeed()) + + repositoryObj.Status = v1alpha1.OCMRepositoryStatus{ + EffectiveOCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[0].Name, + Namespace: secrets[0].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[1].Name, + Namespace: secrets[1].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[2].Name, + Namespace: secrets[2].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[0].Name, + Namespace: configs[1].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[1].Name, + Namespace: secrets[1].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[2].Name, + Namespace: configs[2].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + }, + } + + conditions.MarkTrue(repositoryObj, "Ready", "ready", "message") + Expect(k8sClient.Status().Update(ctx, repositoryObj)).To(Succeed()) + + testNumber++ + }) + + AfterEach(func() { + // make sure the repo is still ready + conditions.MarkTrue(repositoryObj, "Ready", "ready", "message") + Expect(k8sClient.Status().Update(ctx, repositoryObj)).To(Succeed()) + cleanupTestConfigsAndSecrets(ctx, configs, secrets) + }) + + It("component resolves and propagates config from repository", func() { + By("creating a component") + component := &v1alpha1.Component{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + }, + Spec: v1alpha1.ComponentSpec{ + RepositoryRef: v1alpha1.ObjectKey{ + Namespace: Namespace, + Name: repositoryName, + }, + Component: Component, + Semver: "1.0.0", + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: v1alpha1.KindOCMRepository, + Name: repositoryName, + Namespace: Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + }, + Interval: metav1.Duration{Duration: time.Minute * 10}, + }, + Status: v1alpha1.ComponentStatus{}, + } + Expect(k8sClient.Create(ctx, component)).To(Succeed()) + + Eventually(komega.Object(component), "15s").Should( + HaveField("Status.EffectiveOCMConfig", ConsistOf( + v1alpha1.OCMConfiguration{ + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[2].Name, + Namespace: secrets[2].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + v1alpha1.OCMConfiguration{ + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[2].Name, + Namespace: configs[2].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + ))) + }) + }) }) + +func createTestConfigsAndSecrets(ctx context.Context) (configs []*corev1.ConfigMap, secrets []*corev1.Secret) { + const ( + Config1 = "config1" + Config2 = "config2" + Config3 = "config3" + + Secret1 = "secret1" + Secret2 = "secret2" + Secret3 = "secret3" + ) + + By("setup configs") + config1 := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Config1, + }, + Data: map[string]string{ + v1alpha1.OCMConfigKey: ` +type: generic.config.ocm.software/v1 +sets: + set1: + description: set1 + configurations: + - type: credentials.config.ocm.software + consumers: + - identity: + type: MavenRepository + hostname: example.com + pathprefix: path/ocm + credentials: + - type: Credentials + properties: + username: testuser1 + password: testpassword1 +`, + }, + } + configs = append(configs, config1) + Expect(k8sClient.Create(ctx, config1)).To(Succeed()) + + config2 := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Config2, + }, + Data: map[string]string{ + v1alpha1.OCMConfigKey: ` +type: generic.config.ocm.software/v1 +sets: + set2: + description: set2 + configurations: + - type: credentials.config.ocm.software + consumers: + - identity: + type: MavenRepository + hostname: example.com + pathprefix: path/ocm + credentials: + - type: Credentials + properties: + username: testuser1 + password: testpassword1 +`, + }, + } + configs = append(configs, config2) + Expect(k8sClient.Create(ctx, config2)).To(Succeed()) + + config3 := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Config3, + }, + Data: map[string]string{ + v1alpha1.OCMConfigKey: ` +type: generic.config.ocm.software/v1 +sets: + set3: + description: set3 + configurations: + - type: credentials.config.ocm.software + consumers: + - identity: + type: MavenRepository + hostname: example.com + pathprefix: path/ocm + credentials: + - type: Credentials + properties: + username: testuser1 + password: testpassword1 +`, + }, + } + configs = append(configs, config3) + Expect(k8sClient.Create(ctx, config3)).To(Succeed()) + + By("setup secrets") + secret1 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Secret1, + }, + Data: map[string][]byte{ + v1alpha1.OCMConfigKey: []byte(` +type: credentials.config.ocm.software +consumers: +- identity: + type: MavenRepository + hostname: example.com + pathprefix: path1 + credentials: + - type: Credentials + properties: + username: testuser1 + password: testpassword1 +`), + }, + } + secrets = append(secrets, secret1) + Expect(k8sClient.Create(ctx, secret1)).To(Succeed()) + + secret2 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Secret2, + }, + Data: map[string][]byte{ + v1alpha1.OCMConfigKey: []byte(` +type: credentials.config.ocm.software +consumers: +- identity: + type: MavenRepository + hostname: example.com + pathprefix: path2 + credentials: + - type: Credentials + properties: + username: testuser2 + password: testpassword2 +`), + }, + } + secrets = append(secrets, secret2) + Expect(k8sClient.Create(ctx, secret2)).To(Succeed()) + + secret3 := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Secret3, + }, + Data: map[string][]byte{ + v1alpha1.OCMConfigKey: []byte(` +type: credentials.config.ocm.software +consumers: +- identity: + type: MavenRepository + hostname: example.com + pathprefix: path3 + credentials: + - type: Credentials + properties: + username: testuser3 + password: testpassword3 +`), + }, + } + secrets = append(secrets, &secret3) + Expect(k8sClient.Create(ctx, &secret3)).To(Succeed()) + + return configs, secrets +} + +func cleanupTestConfigsAndSecrets(ctx context.Context, configs []*corev1.ConfigMap, secrets []*corev1.Secret) { + for _, config := range configs { + Expect(k8sClient.Delete(ctx, config)).To(Succeed()) + } + for _, secret := range secrets { + Expect(k8sClient.Delete(ctx, secret)).To(Succeed()) + } +} diff --git a/internal/controller/ocmrepository/controller.go b/internal/controller/ocmrepository/controller.go index 66b96a3d..1e0ef337 100644 --- a/internal/controller/ocmrepository/controller.go +++ b/internal/controller/ocmrepository/controller.go @@ -125,8 +125,16 @@ func (r *Reconciler) reconcileRepository(ctx context.Context, octx ocmctx.Contex // automatically close the session when the ocm context is closed in the above defer octx.Finalizer().Close(session) - err := ocm.ConfigureOCMContext(ctx, r, octx, ocmRepo, ocmRepo) + configs, err := ocm.GetEffectiveConfig(ctx, r.GetClient(), ocmRepo) if err != nil { + status.MarkNotReady(r.GetEventRecorder(), ocmRepo, v1alpha1.ConfigureContextFailedReason, err.Error()) + + return ctrl.Result{}, nil + } + err = ocm.ConfigureContext(ctx, octx, r.GetClient(), configs) + if err != nil { + status.MarkNotReady(r.GetEventRecorder(), ocmRepo, v1alpha1.ConfigureContextFailedReason, err.Error()) + return ctrl.Result{}, err } @@ -135,7 +143,7 @@ func (r *Reconciler) reconcileRepository(ctx context.Context, octx ocmctx.Contex return ctrl.Result{}, err } - r.fillRepoStatusFromSpec(ocmRepo) + r.fillRepoStatusFromSpec(ocmRepo, configs) status.MarkReady(r.EventRecorder, ocmRepo, "Successfully reconciled") @@ -166,10 +174,10 @@ func (r *Reconciler) validate(octx ocmctx.Context, session ocmctx.Session, ocmRe return nil } -func (r *Reconciler) fillRepoStatusFromSpec(ocmRepo *v1alpha1.OCMRepository) { - ocmRepo.SetEffectiveConfigSet() - ocmRepo.SetEffectiveConfigRefs() - ocmRepo.SetEffectiveSecretRefs() +func (r *Reconciler) fillRepoStatusFromSpec(ocmRepo *v1alpha1.OCMRepository, + configs []v1alpha1.OCMConfiguration, +) { + ocmRepo.Status.EffectiveOCMConfig = configs } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/ocmrepository/controller_test.go b/internal/controller/ocmrepository/controller_test.go index 7b86c6c4..eaef866c 100644 --- a/internal/controller/ocmrepository/controller_test.go +++ b/internal/controller/ocmrepository/controller_test.go @@ -138,9 +138,9 @@ var _ = Describe("OCMRepository Controller", func() { }) }) - Describe("Reconsiling a valid OCMRepository", func() { + Describe("Reconciling a valid OCMRepository", func() { - Context("When SecretRefs and ConfigRefs properly set", func() { + Context("When ConfigRefs properly set", func() { It("OCMRepository can be reconciled", func() { By("creating secret and config objects") @@ -152,35 +152,122 @@ var _ = Describe("OCMRepository Controller", func() { repoName := TestOCMRepositoryObj + "-all-fields" ocmRepo = newTestOCMRepository(TestNamespaceOCMRepo, repoName, &specdata) - By("adding SecretRefs") - ocmRepo.Spec.SecretRefs = append(ocmRepo.Spec.SecretRefs, corev1.LocalObjectReference{Name: secrets[0].Name}) - ocmRepo.Spec.SecretRefs = append(ocmRepo.Spec.SecretRefs, corev1.LocalObjectReference{Name: secrets[1].Name}) - ocmRepo.Spec.SecretRefs = append(ocmRepo.Spec.SecretRefs, corev1.LocalObjectReference{Name: secrets[2].Name}) - - By("adding ConfigRefs") - ocmRepo.Spec.ConfigRefs = append(ocmRepo.Spec.ConfigRefs, corev1.LocalObjectReference{Name: configs[0].Name}) - ocmRepo.Spec.ConfigRefs = append(ocmRepo.Spec.ConfigRefs, corev1.LocalObjectReference{Name: configs[1].Name}) - ocmRepo.Spec.ConfigRefs = append(ocmRepo.Spec.ConfigRefs, corev1.LocalObjectReference{Name: configs[2].Name}) - - By("adding ConfigSet") - configSet := "set1" - ocmRepo.Spec.ConfigSet = &configSet + By("adding config and secret refs") + ocmRepo.Spec.OCMConfig = append(ocmRepo.Spec.OCMConfig, []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[0].Name, + Namespace: secrets[0].Namespace, + }, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[1].Name, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "Secret", + Name: secrets[2].Name, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[0].Name, + Namespace: configs[0].Namespace, + }, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[1].Name, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "ConfigMap", + Name: configs[2].Name, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + }...) By("creating OCMRepository object") Expect(k8sClient.Create(ctx, ocmRepo)).To(Succeed()) - By("check that the SecretRefs and ConfigRefs are in the status") - Eventually(komega.Object(ocmRepo), "1m").Should(And( + By("check that the ConfigRefs are in the status") + expectedEffectiveOCMConfig := []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[0].Name, + Namespace: secrets[0].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[1].Name, + Namespace: secrets[1].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[2].Name, + Namespace: secrets[2].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[0].Name, + Namespace: configs[0].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[1].Name, + Namespace: configs[1].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configs[2].Name, + Namespace: configs[2].Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + } + Eventually(komega.Object(ocmRepo), "15s").Should(And( HaveField("Status.Conditions", ContainElement( And(HaveField("Type", Equal(meta.ReadyCondition)), HaveField("Status", Equal(metav1.ConditionTrue))), )), - HaveField("Status.SecretRefs", ContainElement(Equal(ocmRepo.Spec.SecretRefs[0]))), - HaveField("Status.SecretRefs", ContainElement(Equal(ocmRepo.Spec.SecretRefs[1]))), - HaveField("Status.SecretRefs", ContainElement(Equal(ocmRepo.Spec.SecretRefs[2]))), - HaveField("Status.ConfigRefs", ContainElement(Equal(ocmRepo.Spec.ConfigRefs[0]))), - HaveField("Status.ConfigRefs", ContainElement(Equal(ocmRepo.Spec.ConfigRefs[1]))), - HaveField("Status.ConfigRefs", ContainElement(Equal(ocmRepo.Spec.ConfigRefs[2]))), - HaveField("Status.ConfigSet", Equal(*ocmRepo.Spec.ConfigSet)), + HaveField("Status.EffectiveOCMConfig", ConsistOf(expectedEffectiveOCMConfig)), + HaveField("GetEffectiveOCMConfig()", ConsistOf(expectedEffectiveOCMConfig)), )) By("cleanup secret and config objects") @@ -252,7 +339,6 @@ var _ = Describe("OCMRepository Controller", func() { }).WithTimeout(10 * time.Second).Should(Succeed()) }) }) - }) }) diff --git a/internal/controller/replication/controller.go b/internal/controller/replication/controller.go index 95feda08..0385c2c2 100644 --- a/internal/controller/replication/controller.go +++ b/internal/controller/replication/controller.go @@ -21,7 +21,6 @@ import ( "encoding/json" "errors" "fmt" - "slices" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -161,11 +160,18 @@ func (r *Reconciler) reconcile(ctx context.Context, replication *v1alpha1.Replic return ctrl.Result{RequeueAfter: replication.GetRequeueAfter()}, nil } - historyRecord, err := r.transfer(ctx, replication, comp, repo) + configs, err := ocm.GetEffectiveConfig(ctx, r.GetClient(), replication) + if err != nil { + status.MarkNotReady(r.GetEventRecorder(), replication, v1alpha1.ConfigureContextFailedReason, err.Error()) + + return ctrl.Result{}, err + } + + historyRecord, err := r.transfer(ctx, configs, replication, comp, repo) if err != nil { historyRecord.Error = err.Error() historyRecord.EndTime = metav1.Now() - r.setReplicationStatus(replication, historyRecord) + r.setReplicationStatus(configs, replication, historyRecord) logger.Info("error transferring component", "component", comp.Name, "targetRepository", repo.Name) status.MarkNotReady(r.EventRecorder, replication, v1alpha1.ReplicationFailedReason, err.Error()) @@ -174,13 +180,13 @@ func (r *Reconciler) reconcile(ctx context.Context, replication *v1alpha1.Replic } // Update status - r.setReplicationStatus(replication, historyRecord) + r.setReplicationStatus(configs, replication, historyRecord) status.MarkReady(r.EventRecorder, replication, "Successfully replicated %s to %s", comp.Name, repo.Name) return ctrl.Result{RequeueAfter: replication.GetRequeueAfter()}, nil } -func (r *Reconciler) transfer(ctx context.Context, +func (r *Reconciler) transfer(ctx context.Context, configs []v1alpha1.OCMConfiguration, replication *v1alpha1.Replication, comp *v1alpha1.Component, targetOCMRepo *v1alpha1.OCMRepository, ) (historyRecord v1alpha1.TransferStatus, retErr error) { // DefaultContext is essentially the same as the extended context created here. The difference is, if we @@ -202,9 +208,11 @@ func (r *Reconciler) transfer(ctx context.Context, TargetRepositorySpec: string(targetOCMRepo.Spec.RepositorySpec.Raw), } - err := r.ConfigureOCMContext(ctx, octx, replication, comp, targetOCMRepo) + err := ocm.ConfigureContext(ctx, octx, r.GetClient(), configs) if err != nil { - return historyRecord, fmt.Errorf("cannot configure OCM context: %w", err) + status.MarkNotReady(r.GetEventRecorder(), replication, v1alpha1.ConfigureContextFailedReason, err.Error()) + + return historyRecord, fmt.Errorf("cannot configure context: %w", err) } sourceRepo, err := session.LookupRepositoryForConfig(octx, comp.Status.Component.RepositorySpec.Raw) @@ -280,44 +288,10 @@ func (r *Reconciler) validate(session ocmctx.Session, repo ocmctx.Repository, co return nil } -func (r *Reconciler) ConfigureOCMContext(ctx context.Context, octx ocmctx.Context, - replication *v1alpha1.Replication, comp *v1alpha1.Component, targetOCMRepo *v1alpha1.OCMRepository, -) error { - sourceOCMRepo := &v1alpha1.OCMRepository{} - if err := r.Get(ctx, types.NamespacedName{ - Namespace: comp.Spec.RepositoryRef.Namespace, - Name: comp.Spec.RepositoryRef.Name, - }, sourceOCMRepo); err != nil { - return fmt.Errorf("failed to configure ocmcontext: %w", err) - } - - err := ocm.ConfigureOCMContext(ctx, r, octx, comp, sourceOCMRepo) - if err != nil { - return fmt.Errorf("failed to configure ocmcontext: %w", err) - } - - err = ocm.ConfigureOCMContext(ctx, r, octx, targetOCMRepo, targetOCMRepo) - if err != nil { - return fmt.Errorf("failed to configure ocmcontext: %w", err) - } - - err = ocm.ConfigureOCMContext(ctx, r, octx, replication, replication) - if err != nil { - return fmt.Errorf("failed to configure ocmcontext: %w", err) - } - - return nil -} - -func (r *Reconciler) setReplicationStatus(replication *v1alpha1.Replication, historyRecord v1alpha1.TransferStatus) { +func (r *Reconciler) setReplicationStatus(configs []v1alpha1.OCMConfiguration, replication *v1alpha1.Replication, historyRecord v1alpha1.TransferStatus) { replication.AddHistoryRecord(historyRecord) - replication.Status.ConfigRefs = slices.Clone(replication.Spec.ConfigRefs) - replication.Status.SecretRefs = slices.Clone(replication.Spec.SecretRefs) - - if replication.Spec.ConfigSet != nil { - replication.Status.ConfigSet = *replication.Spec.ConfigSet - } + replication.Status.EffectiveOCMConfig = configs } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/replication/controller_test.go b/internal/controller/replication/controller_test.go index ece04366..26e7f338 100644 --- a/internal/controller/replication/controller_test.go +++ b/internal/controller/replication/controller_test.go @@ -269,8 +269,13 @@ var _ = Describe("Replication Controller", func() { By("Create and reconcile Replication resource") replication := newTestReplication(testNamespace, replResourceName, compResourceName, targetRepoResourceName) - replication.Spec.ConfigRefs = []corev1.LocalObjectReference{ - {Name: optResourceName}, + replication.Spec.OCMConfig = []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "ConfigMap", + Name: optResourceName, + }, + }, } Expect(k8sClient.Create(ctx, replication)).To(Succeed()) diff --git a/internal/controller/resource/resource_controller.go b/internal/controller/resource/resource_controller.go index 1b555e54..87514797 100644 --- a/internal/controller/resource/resource_controller.go +++ b/internal/controller/resource/resource_controller.go @@ -23,7 +23,6 @@ import ( "fmt" "os" "path/filepath" - "slices" "strings" "github.com/fluxcd/pkg/runtime/conditions" @@ -231,7 +230,16 @@ func (r *Reconciler) reconcileResource(ctx context.Context, octx ocmctx.Context, // automatically close the session when the ocm context is closed in the above defer octx.Finalizer().Close(session) - if err := ocm.ConfigureOCMContext(ctx, r, octx, resource, component); err != nil { + configs, err := ocm.GetEffectiveConfig(ctx, r.GetClient(), resource) + if err != nil { + status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.ConfigureContextFailedReason, err.Error()) + + return ctrl.Result{}, err + } + err = ocm.ConfigureContext(ctx, octx, r.GetClient(), configs) + if err != nil { + status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.ConfigureContextFailedReason, err.Error()) + return ctrl.Result{}, err } @@ -313,7 +321,7 @@ func (r *Reconciler) reconcileResource(ctx context.Context, octx ocmctx.Context, } // Update status - if err = setResourceStatus(ctx, resource, resourceAccess); err != nil { + if err = setResourceStatus(ctx, configs, resource, resourceAccess); err != nil { status.MarkNotReady(r.EventRecorder, component, v1alpha1.StatusSetFailedReason, err.Error()) return ctrl.Result{}, fmt.Errorf("failed to set resource status: %w", err) @@ -546,7 +554,7 @@ func reconcileArtifact( } // setResourceStatus updates the resource status with the all required information. -func setResourceStatus(ctx context.Context, resource *v1alpha1.Resource, resourceAccess ocmctx.ResourceAccess) error { +func setResourceStatus(ctx context.Context, configs []v1alpha1.OCMConfiguration, resource *v1alpha1.Resource, resourceAccess ocmctx.ResourceAccess) error { log.FromContext(ctx).V(1).Info("updating resource status") // Get the access spec from the resource access @@ -569,8 +577,7 @@ func setResourceStatus(ctx context.Context, resource *v1alpha1.Resource, resourc Digest: resourceAccess.Meta().Digest.String(), } - resource.Status.ConfigRefs = slices.Clone(resource.Spec.ConfigRefs) - resource.Status.SecretRefs = slices.Clone(resource.Spec.SecretRefs) + resource.Status.EffectiveOCMConfig = configs return nil } diff --git a/pkg/ocm/configure.go b/pkg/ocm/configure.go deleted file mode 100644 index 929717b8..00000000 --- a/pkg/ocm/configure.go +++ /dev/null @@ -1,52 +0,0 @@ -package ocm - -import ( - "context" - "fmt" - - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "ocm.software/ocm/api/ocm" - - "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" -) - -func ConfigureOCMContext(ctx context.Context, r Reconciler, octx ocm.Context, - obj v1alpha1.OCMK8SObject, def v1alpha1.OCMK8SObject, -) error { - secrets, err := GetSecrets(ctx, r.GetClient(), GetEffectiveSecretRefs(ctx, obj, def)) - if err != nil { - status.MarkNotReady(r.GetEventRecorder(), obj, v1alpha1.SecretFetchFailedReason, err.Error()) - - return fmt.Errorf("failed to get secrets: %w", err) - } - - configs, err := GetConfigMaps(ctx, r.GetClient(), GetEffectiveConfigRefs(ctx, obj, def)) - if err != nil { - status.MarkNotReady(r.GetEventRecorder(), obj, v1alpha1.ConfigFetchFailedReason, err.Error()) - - return fmt.Errorf("failed to get configmaps: %w", err) - } - - set := GetEffectiveConfigSet(ctx, obj, def) - - var signingkeys []Verification - if vprov, ok := obj.(v1alpha1.VerificationProvider); ok { - signingkeys, err = GetVerifications(ctx, r.GetClient(), vprov) - if err != nil { - status.MarkNotReady(r.GetEventRecorder(), obj, v1alpha1.VerificationsInvalidReason, err.Error()) - - return err - } - } - - err = ConfigureContext(ctx, octx, signingkeys, secrets, configs, set) - if err != nil { - status.MarkNotReady(r.GetEventRecorder(), obj, v1alpha1.ConfigureContextFailedReason, err.Error()) - - return reconcile.TerminalError(fmt.Errorf("failed to configure ocm context: %w", err)) - } - - return nil -} diff --git a/pkg/ocm/lookup.go b/pkg/ocm/lookup.go deleted file mode 100644 index 27b66a0e..00000000 --- a/pkg/ocm/lookup.go +++ /dev/null @@ -1,119 +0,0 @@ -package ocm - -import ( - "context" - "fmt" - - "github.com/mandelsoft/goutils/sliceutils" - corev1 "k8s.io/api/core/v1" - ctrl "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" -) - -// GetEffectiveSecretRefs returns either the secrets from obj's spec or the effective secrets of the def (= default) -// SecretRefProvider. -func GetEffectiveSecretRefs(_ context.Context, - obj v1alpha1.SecretRefProvider, def ...v1alpha1.SecretRefProvider, -) []ctrl.ObjectKey { - ns := obj.GetNamespace() - refs := obj.GetSecretRefs() - - // if no secrets were specified on the object itself, default to the effective secrets of the default object - // (which is typically the predecessor object in the processing chain, so for a component, it's a repository) - if len(refs) == 0 && len(def) > 0 { - ns = def[0].GetNamespace() - refs = def[0].GetEffectiveSecretRefs() - } - - secretRefs := sliceutils.Transform(refs, func(ref corev1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{ - Namespace: ns, - Name: ref.Name, - } - }) - - return secretRefs -} - -// GetEffectiveConfigRefs returns either the configs from obj's spec or the effective configs of the def (= default) -// ConfigRefProvider. -func GetEffectiveConfigRefs(_ context.Context, - obj v1alpha1.ConfigRefProvider, def ...v1alpha1.ConfigRefProvider, -) []ctrl.ObjectKey { - ns := obj.GetNamespace() - refs := obj.GetConfigRefs() - - // if no secrets were specified on the object itself, default to the effective secrets of the default object - // (which is typically the predecessor object in the processing chain, so for a component, it's a repository) - if len(refs) == 0 && len(def) > 0 { - ns = def[0].GetNamespace() - refs = def[0].GetEffectiveConfigRefs() - } - - configRefs := sliceutils.Transform(refs, func(ref corev1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{ - Namespace: ns, - Name: ref.Name, - } - }) - - return configRefs -} - -func GetEffectiveConfigSet(_ context.Context, - obj v1alpha1.ConfigSetProvider, def v1alpha1.ConfigSetProvider, -) string { - set := obj.GetConfigSet() - if set != nil { - return *set - } - - return def.GetEffectiveConfigSet() -} - -// GetSecrets returns the secrets referenced by the secretRefs. -func GetSecrets(ctx context.Context, client ctrl.Client, - secretRefs []ctrl.ObjectKey, -) ([]*corev1.Secret, error) { - secrets, err := get[corev1.Secret](ctx, client, secretRefs) - if err != nil { - return nil, err - } - - return secrets, nil -} - -// GetConfigMaps returns the secrets referenced by the secretRefs. -func GetConfigMaps(ctx context.Context, client ctrl.Client, - configRefs []ctrl.ObjectKey, -) ([]*corev1.ConfigMap, error) { - configs, err := get[corev1.ConfigMap](ctx, client, configRefs) - if err != nil { - return nil, err - } - - return configs, nil -} - -type ObjectPointerType[T any] interface { - *T - ctrl.Object -} - -func get[T any, P ObjectPointerType[T]](ctx context.Context, client ctrl.Client, - refs []ctrl.ObjectKey, -) ([]P, error) { - objs := make([]P, 0, len(refs)) - for _, ref := range refs { - var _obj T - obj := P(&_obj) - - if err := client.Get(ctx, ref, obj); err != nil { - return nil, fmt.Errorf("failed to locate object: %w", err) - } - objs = append(objs, obj) - } - - return objs, nil -} diff --git a/pkg/ocm/lookup_test.go b/pkg/ocm/lookup_test.go deleted file mode 100644 index 7c617610..00000000 --- a/pkg/ocm/lookup_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package ocm - -import ( - "context" - - "github.com/mandelsoft/goutils/sliceutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" -) - -const ( - SecretRef1 = "secret1" - SecretRef2 = "secret2" - SecretRef3 = "secret3" - - ConfigRef1 = "config1" - ConfigRef2 = "config2" - ConfigRef3 = "config3" - - Namespace = "default" -) - -var ( - ConfigSet1 = "set1" - ConfigSet2 = "set2" -) - -var _ = Describe("k8s utils", func() { - - Context("get effective refs", func() { - var ( - ctx context.Context - - repo v1alpha1.OCMRepository - defcomp v1alpha1.Component - nodefcomp v1alpha1.Component - ) - - BeforeEach(func() { - ctx = context.Background() - - repo = v1alpha1.OCMRepository{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - }, - Spec: v1alpha1.OCMRepositorySpec{ - SecretRefs: []v1.LocalObjectReference{ - {Name: SecretRef1}, - {Name: SecretRef2}, - {Name: SecretRef3}, - }, - ConfigRefs: []v1.LocalObjectReference{ - {Name: ConfigRef1}, - {Name: ConfigRef2}, - {Name: ConfigRef3}, - }, - ConfigSet: &ConfigSet1, - }, - Status: v1alpha1.OCMRepositoryStatus{ - SecretRefs: []v1.LocalObjectReference{ - {Name: SecretRef1}, - {Name: SecretRef2}, - {Name: SecretRef3}, - }, - ConfigRefs: []v1.LocalObjectReference{ - {Name: ConfigRef1}, - {Name: ConfigRef2}, - {Name: ConfigRef3}, - }, - ConfigSet: ConfigSet1, - }, - } - - defcomp = v1alpha1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - }, - Spec: v1alpha1.ComponentSpec{}, - } - - nodefcomp = v1alpha1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - }, - Spec: v1alpha1.ComponentSpec{ - SecretRefs: []v1.LocalObjectReference{ - {Name: SecretRef1}, - }, - ConfigRefs: []v1.LocalObjectReference{ - {Name: ConfigRef1}, - }, - ConfigSet: &ConfigSet2, - }, - } - }) - - It("get effective secret refs", func() { - keys := GetEffectiveSecretRefs(ctx, &nodefcomp, &repo) - expectedKeys := sliceutils.Transform(nodefcomp.Spec.SecretRefs, func(ref v1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{Namespace: nodefcomp.Namespace, Name: ref.Name} - }) - Expect(keys).To(ConsistOf(expectedKeys)) - }) - It("get effective secret refs by defaulting", func() { - keys := GetEffectiveSecretRefs(ctx, &defcomp, &repo) - expectedKeys := sliceutils.Transform(repo.Status.SecretRefs, func(ref v1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{Namespace: repo.Namespace, Name: ref.Name} - }) - Expect(keys).To(ConsistOf(expectedKeys)) - }) - It("get effective secret ref with no default", func() { - keys := GetEffectiveSecretRefs(ctx, &repo) - expectedKeys := sliceutils.Transform(repo.Spec.SecretRefs, func(ref v1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{Namespace: repo.Namespace, Name: ref.Name} - }) - Expect(keys).To(ConsistOf(expectedKeys)) - }) - It("get effective config refs", func() { - keys := GetEffectiveConfigRefs(ctx, &nodefcomp, &repo) - expectedKeys := sliceutils.Transform(nodefcomp.Spec.ConfigRefs, func(ref v1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{Namespace: nodefcomp.Namespace, Name: ref.Name} - }) - Expect(keys).To(ConsistOf(expectedKeys)) - }) - It("get effective config refs by defaulting", func() { - keys := GetEffectiveConfigRefs(ctx, &defcomp, &repo) - expectedKeys := sliceutils.Transform(repo.Status.ConfigRefs, func(ref v1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{Namespace: repo.Namespace, Name: ref.Name} - }) - Expect(keys).To(ConsistOf(expectedKeys)) - }) - It("get effective config refs with no default", func() { - keys := GetEffectiveConfigRefs(ctx, &repo) - expectedKeys := sliceutils.Transform(repo.Spec.ConfigRefs, func(ref v1.LocalObjectReference) ctrl.ObjectKey { - return ctrl.ObjectKey{Namespace: repo.Namespace, Name: ref.Name} - }) - Expect(keys).To(ConsistOf(expectedKeys)) - }) - It("get effective config set", func() { - set := GetEffectiveConfigSet(ctx, &nodefcomp, &repo) - expectedSet := *nodefcomp.Spec.ConfigSet - Expect(set).To(Equal(expectedSet)) - }) - It("get effective config set by defaulting", func() { - set := GetEffectiveConfigSet(ctx, &defcomp, &repo) - expectedSet := *repo.Spec.ConfigSet - Expect(set).To(Equal(expectedSet)) - }) - }) -}) diff --git a/pkg/ocm/ocm.go b/pkg/ocm/ocm.go index 42618621..528ace6e 100644 --- a/pkg/ocm/ocm.go +++ b/pkg/ocm/ocm.go @@ -5,10 +5,10 @@ import ( "encoding/json" "fmt" "regexp" - "strings" "github.com/Masterminds/semver/v3" "github.com/mandelsoft/goutils/matcher" + "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" corev1 "k8s.io/api/core/v1" "ocm.software/ocm/api/credentials/extensions/repositories/dockerconfig" "ocm.software/ocm/api/ocm" @@ -16,93 +16,114 @@ import ( "ocm.software/ocm/api/ocm/cpi" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" utils "ocm.software/ocm/api/ocm/ocmutils" - "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/ocm/tools/signing" common "ocm.software/ocm/api/utils/misc" "ocm.software/ocm/api/utils/runtime" "ocm.software/ocm/api/utils/semverutils" ctrl "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" ) -// Verification is an internal representation of v1alpha1.Verification where the public key is already extracted from -// the value or secret. -type Verification struct { - Signature string - PublicKey []byte -} - -// ConfigureContext reads all the secrets and config maps, checks them for -// known configuration types and applies them to the context. -func ConfigureContext(ctx context.Context, octx ocm.Context, verifications []Verification, - secrets []*corev1.Secret, configmaps []*corev1.ConfigMap, configset ...string, +// ConfigureContext adds all the configuration data found in the config maps and +// secrets specified through the OCMConfiguration objects to the ocm context. +// NOTE: ConfigMaps and Secrets are slightly different, since secrets can also +// contain credentials in form of docker config jsons. +// +// Furthermore, it registers the public keys for the verification of signatures +// in the ocm context. +func ConfigureContext(ctx context.Context, octx ocm.Context, client ctrl.Client, + configs []v1alpha1.OCMConfiguration, verifications ...[]Verification, ) error { - err := ConfigureContextForSecrets(ctx, octx, secrets) - if err != nil { - return err - } - err = ConfigureContextForConfigMaps(ctx, octx, configmaps) - if err != nil { - return err - } + var obj ctrl.Object + for _, config := range configs { + switch config.Kind { + case "Secret": + obj = &corev1.Secret{} + case "ConfigMap": + obj = &corev1.ConfigMap{} + default: + return fmt.Errorf("unsupported configuration kind: %s", config.Kind) + } - var set string - if len(configset) > 0 { - set = configset[0] - } - if set != "" { - err := octx.ConfigContext().ApplyConfigSet(set) + err := client.Get(ctx, ctrl.ObjectKey{ + Namespace: config.Namespace, + Name: config.Name, + }, obj) if err != nil { - return fmt.Errorf("cannot apply ocm config set %s: %w", set, err) + return fmt.Errorf("configure context cannot fetch %s "+ + "%s/%s: %w", config.Kind, config.Namespace, config.Name, err) + } + err = ConfigureContextForSecretOrConfigMap(ctx, octx, obj) + if err != nil { + return err } } - // If we were to introduce further functionality into the controller that have to use the signing registry we - // retrieve from the context here (e.g. signing), we would have to change the coding so that the signing operation - // and the verification operation use dedicated signing stores. - signinfo := signingattr.Get(octx) + // If we were to introduce further functionality into the controller that + // have to use the signing registry we retrieve from the context here + // (e.g. signing), we would have to change the coding so that the signing + // operation and the verification operation use dedicated signing stores. + if len(verifications) > 0 { + if len(verifications) > 1 { + return fmt.Errorf("only one verification list is supported") + } + signInfo := signingattr.Get(octx) - for _, verification := range verifications { - signinfo.RegisterPublicKey(verification.Signature, verification.PublicKey) + for _, v := range verifications[0] { + signInfo.RegisterPublicKey(v.Signature, v.PublicKey) + } } return nil } -func ConfigureContextForSecrets(_ context.Context, octx ocm.Context, secrets []*corev1.Secret) error { - history := map[ctrl.ObjectKey]struct{}{} - for _, secret := range secrets { - // track that the list does not contain the same secret twice as this could lead to unexpected behavior - key := ctrl.ObjectKeyFromObject(secret) - if _, ok := history[key]; ok { - return fmt.Errorf("the same secret cannot be referenced twice") - } - history[key] = struct{}{} +// ConfigureContextForSecretOrConfigMap wraps ConfigureContextForSecret and +// ConfigureContextForConfigMaps to configure the ocm context. +func ConfigureContextForSecretOrConfigMap(ctx context.Context, octx ocm.Context, obj ctrl.Object) error { + var err error + switch o := obj.(type) { + case *corev1.Secret: + err = ConfigureContextForSecret(ctx, octx, o) + case *corev1.ConfigMap: + err = ConfigureContextForConfigMaps(ctx, octx, o) + default: + return fmt.Errorf("unsupported configuration object type: %T", obj) + } + if err != nil { + return fmt.Errorf("configure context failed for %s "+ + "%s/%s: %w", obj.GetObjectKind(), obj.GetNamespace(), obj.GetName(), err) + } - if dockerConfigBytes, ok := secret.Data[corev1.DockerConfigJsonKey]; ok { - if len(dockerConfigBytes) > 0 { - spec := dockerconfig.NewRepositorySpecForConfig(dockerConfigBytes, true) + return nil +} - if _, err := octx.CredentialsContext().RepositoryForSpec(spec); err != nil { - return fmt.Errorf("cannot create credentials from secret: %w", err) - } +// ConfigureContextForSecret adds the ocm configuration data as well as +// credentials in the docker config json format found in the secret to the +// ocm context. +func ConfigureContextForSecret(_ context.Context, octx ocm.Context, secret *corev1.Secret) error { + if dockerConfigBytes, ok := secret.Data[corev1.DockerConfigJsonKey]; ok { + if len(dockerConfigBytes) > 0 { + spec := dockerconfig.NewRepositorySpecForConfig(dockerConfigBytes, true) + + if _, err := octx.CredentialsContext().RepositoryForSpec(spec); err != nil { + return fmt.Errorf("failed to apply credentials from docker"+ + "config json in secret %s/%s: %w", secret.Namespace, secret.Name, err) } } + } - if ocmConfigBytes, ok := secret.Data[v1alpha1.OCMConfigKey]; ok { - if len(ocmConfigBytes) > 0 { - cfg, err := octx.ConfigContext().GetConfigForData(ocmConfigBytes, runtime.DefaultYAMLEncoding) - if err != nil { - return err - } + if ocmConfigBytes, ok := secret.Data[v1alpha1.OCMConfigKey]; ok { + if len(ocmConfigBytes) > 0 { + cfg, err := octx.ConfigContext().GetConfigForData(ocmConfigBytes, runtime.DefaultYAMLEncoding) + if err != nil { + return fmt.Errorf("failed to deserialize ocm config data in secret "+ + "%s/%s: %w", secret.Namespace, secret.Name, err) + } - err = octx.ConfigContext().ApplyConfig(cfg, fmt.Sprintf("ocm config secret: %s/%s", - secret.Namespace, secret.Name)) - if err != nil { - return err - } + err = octx.ConfigContext().ApplyConfig(cfg, fmt.Sprintf("ocm config secret: %s/%s", + secret.Namespace, secret.Name)) + if err != nil { + return fmt.Errorf("failed to apply ocm config in secret "+ + "%s/%s: %w", secret.Namespace, secret.Name, err) } } } @@ -110,37 +131,91 @@ func ConfigureContextForSecrets(_ context.Context, octx ocm.Context, secrets []* return nil } -func ConfigureContextForConfigMaps(_ context.Context, octx ocm.Context, configmaps []*corev1.ConfigMap) error { - history := map[ctrl.ObjectKey]struct{}{} - for _, configmap := range configmaps { - // track that the list does not contain the same configmap twice as this could lead to unexpected behavior - key := ctrl.ObjectKeyFromObject(configmap) - if _, ok := history[key]; ok { - return fmt.Errorf("the same secret cannot be referenced twice") +// ConfigureContextForConfigMaps adds the ocm configuration data found in the +// secret to the ocm context. +func ConfigureContextForConfigMaps(_ context.Context, octx ocm.Context, configmap *corev1.ConfigMap) error { + ocmConfigData, ok := configmap.Data[v1alpha1.OCMConfigKey] + if !ok { + return fmt.Errorf("ocm configuration config map does not contain key \"%s\"", + v1alpha1.OCMConfigKey) + } + if len(ocmConfigData) > 0 { + cfg, err := octx.ConfigContext().GetConfigForData([]byte(ocmConfigData), nil) + if err != nil { + return fmt.Errorf("failed to deserialize ocm config data in config map "+ + "%s/%s: %w", configmap.Namespace, configmap.Name, err) + } + err = octx.ConfigContext().ApplyConfig(cfg, fmt.Sprintf("%s/%s", + configmap.Namespace, configmap.Name)) + if err != nil { + return fmt.Errorf("failed to apply ocm config in config map "+ + "%s/%s: %w", configmap.Namespace, configmap.Name, err) } - history[key] = struct{}{} + } + + return nil +} + +// GetEffectiveConfig returns the effective configuration for the given config +// ref provider object. Therefore, references to config maps and secrets (that +// are supposed to contain ocm configuration data) are directly returned. +// Furthermore, references to other ocm objects are resolved and their effective +// configuration (so again, config map and secret references) with policy +// propagate are returned. +func GetEffectiveConfig(ctx context.Context, client ctrl.Client, obj v1alpha1.ConfigRefProvider) ([]v1alpha1.OCMConfiguration, error) { + configs := obj.GetSpecifiedOCMConfig() - ocmConfigData, ok := configmap.Data[v1alpha1.OCMConfigKey] - if !ok { - return fmt.Errorf("ocm configuration config map does not contain key \"%s\"", - v1alpha1.OCMConfigKey) + if len(configs) == 0 { + return nil, nil + } + + var refs []v1alpha1.OCMConfiguration + for _, config := range configs { + if config.Namespace == "" { + config.Namespace = obj.GetNamespace() } - if len(ocmConfigData) > 0 { - cfg, err := octx.ConfigContext().GetConfigForData([]byte(ocmConfigData), nil) - if err != nil { - return fmt.Errorf("invalid ocm config in \"%s\" in namespace \"%s\": %w", - configmap.Name, configmap.Namespace, err) + + if config.Kind == "Secret" || config.Kind == "ConfigMap" { + if config.APIVersion == "" { + config.APIVersion = corev1.SchemeGroupVersion.String() } - err = octx.ConfigContext().ApplyConfig(cfg, fmt.Sprintf("%s/%s", - configmap.Namespace, configmap.Name)) - if err != nil { - return fmt.Errorf("cannot apply ocm config in \"%s\" in namespace \"%s\": %w", - configmap.Name, configmap.Namespace, err) + refs = append(refs, config) + } else { + var resource v1alpha1.ConfigRefProvider + if config.APIVersion == "" { + return nil, fmt.Errorf("api version must be set for reference of kind %s", config.Kind) + } + + switch config.Kind { + case v1alpha1.KindOCMRepository: + resource = &v1alpha1.OCMRepository{} + case v1alpha1.KindComponent: + resource = &v1alpha1.Component{} + case v1alpha1.KindResource: + resource = &v1alpha1.Resource{} + case v1alpha1.KindReplication: + resource = &v1alpha1.Replication{} + default: + return nil, fmt.Errorf("unsupported reference kind: %s", config.Kind) + } + + if err := client.Get(ctx, ctrl.ObjectKey{Namespace: config.Namespace, Name: config.Name}, resource); err != nil { + return nil, fmt.Errorf("failed to fetch resource %s: %w", config.Name, err) + } + + for _, ref := range resource.GetEffectiveOCMConfig() { + if ref.Policy == v1alpha1.ConfigurationPolicyPropagate { + // do not propagate the policy of the parent resource but set + // the policy specified in the respective config (of the + // object being reconciled) + ref.Policy = config.Policy + refs = append(refs, ref) + } } } } - return nil + return refs, nil } func RegexpFilter(regex string) (matcher.Matcher[string], error) { @@ -183,34 +258,6 @@ func GetLatestValidVersion(_ context.Context, versions []string, semvers string, return vers[len(vers)-1], nil } -func VerifyComponentVersion(ctx context.Context, cv ocm.ComponentVersionAccess, sigs []string) (*Descriptors, error) { - logger := log.FromContext(ctx).WithName("signature-validation") - - if len(sigs) == 0 || cv == nil { - return nil, nil - } - octx := cv.GetContext() - - resolver := resolvers.NewCompoundResolver(cv.Repository(), octx.GetResolver()) - opts := signing.NewOptions( - signing.Resolver(resolver), - // do we really want to verify the digests here? isn't it sufficient to verify the signatures since - // the digest verification can and has to be done anyways by the resource controller? - signing.VerifyDigests(), - signing.VerifySignature(sigs...), - signing.Recursive(), - ) - - ws := signing.DefaultWalkingState(cv.GetContext()) - _, err := signing.Apply(nil, ws, cv, opts) - if err != nil { - return nil, fmt.Errorf("failed to verify component signatures %s: %w", strings.Join(sigs, ", "), err) - } - logger.Info("successfully verified component signature") - - return &Descriptors{List: signing.ListComponentDescriptors(cv, ws)}, nil -} - func ListComponentDescriptors(_ context.Context, cv ocm.ComponentVersionAccess, r ocm.ComponentVersionResolver) (*Descriptors, error) { descriptors := &Descriptors{} _, err := utils.Walk(nil, cv, r, diff --git a/pkg/ocm/ocm_test.go b/pkg/ocm/ocm_test.go index 5592f3da..81eaff5a 100644 --- a/pkg/ocm/ocm_test.go +++ b/pkg/ocm/ocm_test.go @@ -3,14 +3,21 @@ package ocm_test import ( "context" "encoding/pem" - "github.com/Masterminds/semver/v3" + "github.com/fluxcd/pkg/apis/meta" . "github.com/mandelsoft/goutils/testutils" "github.com/mandelsoft/vfs/pkg/vfs" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" + . "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" + artifactv1 "github.com/openfluxcd/artifact/api/v1alpha1" corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" "ocm.software/ocm/api/datacontext" . "ocm.software/ocm/api/helper/builder" ocmctx "ocm.software/ocm/api/ocm" @@ -18,6 +25,7 @@ import ( resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" "ocm.software/ocm/api/ocm/tools/signing" "ocm.software/ocm/api/tech/maven/identity" "ocm.software/ocm/api/tech/signing/handlers/rsa" @@ -26,20 +34,19 @@ import ( "ocm.software/ocm/api/utils/accessobj" "ocm.software/ocm/api/utils/mime" common "ocm.software/ocm/api/utils/misc" - - "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" - . "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" + ctrl "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) const ( - CTFPath = "/ctf" - Component = "ocm.software/test" - Reference = "referenced-test" - RefComponent = "ocm.software/referenced-test" - Resource = "testresource" - Version1 = "1.0.0-rc.1" - Version2 = "2.0.0" - Version3 = "3.0.0" + CTFPath = "/ctf" + TestComponent = "ocm.software/test" + Reference = "referenced-test" + RefComponent = "ocm.software/referenced-test" + Resource = "testresource" + Version1 = "1.0.0-rc.1" + Version2 = "2.0.0" + Version3 = "3.0.0" Signature1 = "signature1" Signature2 = "signature2" @@ -68,9 +75,11 @@ var _ = Describe("ocm utils", func() { repo ocmctx.Repository cv ocmctx.ComponentVersionAccess - configs []*corev1.ConfigMap + configmaps []*corev1.ConfigMap secrets []*corev1.Secret + configs []v1alpha1.OCMConfiguration verifications []Verification + clnt ctrl.Client ) BeforeEach(func() { @@ -84,14 +93,14 @@ var _ = Describe("ocm utils", func() { privkey3, pubkey3 := Must2(rsa.CreateKeyPair()) env.OCMCommonTransport(CTFPath, accessio.FormatDirectory, func() { - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(Version1, func() { }) }) }) repo = Must(ctf.Open(env, accessobj.ACC_WRITABLE, CTFPath, vfs.FileMode(vfs.O_RDWR), env)) - cv = Must(repo.LookupComponentVersion(Component, Version1)) + cv = Must(repo.LookupComponentVersion(TestComponent, Version1)) _ = Must(signing.SignComponentVersion(cv, Signature1, signing.PrivateKey(Signature1, privkey1))) _ = Must(signing.SignComponentVersion(cv, Signature2, signing.PrivateKey(Signature2, privkey2))) @@ -104,7 +113,9 @@ var _ = Describe("ocm utils", func() { {Signature: Signature3, PublicKey: pem.EncodeToMemory(signutils.PemBlockForPublicKey(pubkey3))}, }...) - By("setup configs") + By("setup configmaps") + builder := fake.NewClientBuilder() + config1 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: Config1, @@ -130,7 +141,8 @@ sets: `, }, } - configs = append(configs, config1) + configmaps = append(configmaps, config1) + builder.WithObjects(config1) config2 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -157,7 +169,8 @@ sets: `, }, } - configs = append(configs, config2) + configmaps = append(configmaps, config2) + builder.WithObjects(config2) config3 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -184,7 +197,8 @@ sets: `, }, } - configs = append(configs, config3) + configmaps = append(configmaps, config3) + builder.WithObjects(config3) By("setup secrets") secret1 := &corev1.Secret{ @@ -208,6 +222,7 @@ consumers: }, } secrets = append(secrets, secret1) + builder.WithObjects(secret1) secret2 := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -230,8 +245,9 @@ consumers: }, } secrets = append(secrets, secret2) + builder.WithObjects(secret2) - secret3 := corev1.Secret{ + secret3 := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: Secret3, }, @@ -251,7 +267,60 @@ consumers: `), }, } - secrets = append(secrets, &secret3) + secrets = append(secrets, secret3) + builder.WithObjects(secret3) + + clnt = builder.Build() + + By("setup configs") + configs = []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[0].Name, + Namespace: secrets[0].Namespace, + }, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + Name: secrets[1].Name, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "Secret", + Name: secrets[2].Name, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configmaps[0].Name, + Namespace: configmaps[0].Namespace, + }, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + Name: configmaps[1].Name, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "ConfigMap", + Name: configmaps[2].Name, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + } }) AfterEach(func() { @@ -262,7 +331,7 @@ consumers: It("configure context", func() { octx := ocmctx.New(datacontext.MODE_EXTENDED) - MustBeSuccessful(ConfigureContext(ctx, octx, verifications, secrets, configs)) + MustBeSuccessful(ConfigureContext(ctx, octx, clnt, configs, verifications)) creds1 := Must(octx.CredentialsContext().GetCredentialsForConsumer(Must(identity.GetConsumerId("https://example.com/path1", "")), identity.IdentityMatcher)) creds2 := Must(octx.CredentialsContext().GetCredentialsForConsumer(Must(identity.GetConsumerId("https://example.com/path2", "")), identity.IdentityMatcher)) @@ -291,6 +360,391 @@ consumers: }) }) + Context("get effective config", func() { + const ( + Namespace = "test-namespace" + Repository = "test-repository" + Component = "test-component" + ConfigMap = "test-configmap" + Secret = "test-secret" + ) + var ( + bldr *fake.ClientBuilder + clnt ctrl.Client + ) + + // setup scheme + scheme := runtime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(artifactv1.AddToScheme(scheme)) + + BeforeEach(func() { + bldr = fake.NewClientBuilder() + bldr.WithScheme(scheme) + }) + + AfterEach(func() { + bldr = nil + clnt = nil + }) + + It("no config", func() { + specdata, err := ocireg.NewRepositorySpec("ocm.software/mock-repo-spec").MarshalJSON() + Expect(err).ToNot(HaveOccurred()) + + repo := v1alpha1.OCMRepository{ + Spec: v1alpha1.OCMRepositorySpec{ + RepositorySpec: &apiextensionsv1.JSON{Raw: specdata}, + }, + } + + config, err := GetEffectiveConfig(ctx, nil, &repo) + Expect(err).ToNot(HaveOccurred()) + Expect(config).To(BeEmpty()) + }) + + It("duplicate config", func() { + specdata, err := ocireg.NewRepositorySpec("ocm.software/mock-repo-spec").MarshalJSON() + Expect(err).ToNot(HaveOccurred()) + + configMap := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: Namespace, + }, + } + + ocmConfig := []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: configMap.APIVersion, + Kind: configMap.Kind, + Name: configMap.Name, + Namespace: configMap.Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: configMap.APIVersion, + Kind: configMap.Kind, + Name: configMap.Name, + Namespace: configMap.Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + } + repo := v1alpha1.OCMRepository{ + Spec: v1alpha1.OCMRepositorySpec{ + RepositorySpec: &apiextensionsv1.JSON{Raw: specdata}, + OCMConfig: ocmConfig, + }, + } + + config, err := GetEffectiveConfig(ctx, nil, &repo) + Expect(err).ToNot(HaveOccurred()) + // Equal instead of consists of because the order of the + // configuration is important + Expect(config).To(Equal(ocmConfig)) + }) + + It("api version defaulting for configmaps", func() { + repo := v1alpha1.OCMRepository{ + Spec: v1alpha1.OCMRepositorySpec{ + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "ConfigMap", + Namespace: Namespace, + Name: ConfigMap, + }, + }, + }, + }, + } + + config, err := GetEffectiveConfig(ctx, nil, &repo) + Expect(err).ToNot(HaveOccurred()) + Expect(config[0].APIVersion).To(Equal(corev1.SchemeGroupVersion.String())) + }) + + It("api version defaulting for secrets", func() { + repo := v1alpha1.OCMRepository{ + Spec: v1alpha1.OCMRepositorySpec{ + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "Secret", + Namespace: Namespace, + Name: Secret, + }, + }, + }, + }, + } + + config, err := GetEffectiveConfig(ctx, nil, &repo) + Expect(err).ToNot(HaveOccurred()) + Expect(config[0].APIVersion).To(Equal(corev1.SchemeGroupVersion.String())) + }) + + It("empty api version for ocm controller kinds", func() { + repo := v1alpha1.OCMRepository{ + Spec: v1alpha1.OCMRepositorySpec{ + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: v1alpha1.KindOCMRepository, + Namespace: Namespace, + Name: Secret, + }, + }, + }, + }, + } + + config, err := GetEffectiveConfig(ctx, nil, &repo) + Expect(err).To(HaveOccurred()) + Expect(config).To(BeNil()) + }) + + It("unsupported api version", func() { + repo := v1alpha1.OCMRepository{ + Spec: v1alpha1.OCMRepositorySpec{ + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + Kind: "Deployment", + Namespace: Namespace, + Name: Secret, + }, + }, + }, + }, + } + + config, err := GetEffectiveConfig(ctx, nil, &repo) + Expect(err).To(HaveOccurred()) + Expect(config).To(BeNil()) + }) + + It("referenced object not found", func() { + configMap := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: Namespace, + }, + } + // do not add the configmap to the client, to check the behaviour + // if the referenced configuration object is not found + // bldr.WithObjects(&configMap) + + ocmConfig := []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: configMap.APIVersion, + Kind: configMap.Kind, + Name: configMap.Name, + Namespace: configMap.Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + } + repo := v1alpha1.OCMRepository{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: v1alpha1.KindOCMRepository, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Repository, + }, + Spec: v1alpha1.OCMRepositorySpec{ + OCMConfig: ocmConfig, + }, + Status: v1alpha1.OCMRepositoryStatus{ + EffectiveOCMConfig: ocmConfig, + }, + } + bldr.WithObjects(&repo) + + comp := v1alpha1.Component{ + Spec: v1alpha1.ComponentSpec{ + RepositoryRef: v1alpha1.ObjectKey{ + Namespace: repo.Namespace, + Name: repo.Name, + }, + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: repo.APIVersion, + Kind: repo.Kind, + Name: repo.Name, + Namespace: repo.Namespace, + }, + }, + }, + }, + } + bldr.WithObjects(&comp) + + clnt = bldr.Build() + config, err := GetEffectiveConfig(ctx, clnt, &comp) + Expect(err).ToNot(HaveOccurred()) + Expect(config).To(BeEmpty()) + }) + + It("referenced object does no propagation", func() { + configMap := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: Namespace, + }, + } + bldr.WithObjects(&configMap) + + ocmConfig := []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: configMap.APIVersion, + Kind: configMap.Kind, + Name: configMap.Name, + Namespace: configMap.Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + } + repo := v1alpha1.OCMRepository{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: v1alpha1.KindOCMRepository, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Repository, + }, + Spec: v1alpha1.OCMRepositorySpec{ + OCMConfig: ocmConfig, + }, + Status: v1alpha1.OCMRepositoryStatus{ + EffectiveOCMConfig: ocmConfig, + }, + } + bldr.WithObjects(&repo) + + comp := v1alpha1.Component{ + Spec: v1alpha1.ComponentSpec{ + RepositoryRef: v1alpha1.ObjectKey{ + Namespace: repo.Namespace, + Name: repo.Name, + }, + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: repo.APIVersion, + Kind: repo.Kind, + Name: repo.Name, + Namespace: repo.Namespace, + }, + }, + }, + }, + } + bldr.WithObjects(&comp) + + clnt = bldr.Build() + config, err := GetEffectiveConfig(ctx, clnt, &comp) + Expect(err).ToNot(HaveOccurred()) + Expect(config).To(BeEmpty()) + }) + + It("referenced object does propagation", func() { + configMap := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: Namespace, + }, + } + bldr.WithObjects(&configMap) + + ocmConfig := []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: configMap.APIVersion, + Kind: configMap.Kind, + Name: configMap.Name, + Namespace: configMap.Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyPropagate, + }, + } + repo := v1alpha1.OCMRepository{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: v1alpha1.KindOCMRepository, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: Namespace, + Name: Repository, + }, + Spec: v1alpha1.OCMRepositorySpec{ + OCMConfig: ocmConfig, + }, + Status: v1alpha1.OCMRepositoryStatus{ + EffectiveOCMConfig: ocmConfig, + }, + } + bldr.WithObjects(&repo) + + comp := v1alpha1.Component{ + Spec: v1alpha1.ComponentSpec{ + RepositoryRef: v1alpha1.ObjectKey{ + Namespace: repo.Namespace, + Name: repo.Name, + }, + OCMConfig: []v1alpha1.OCMConfiguration{ + { + NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ + APIVersion: repo.APIVersion, + Kind: repo.Kind, + Name: repo.Name, + Namespace: repo.Namespace, + }, + Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, + }, + }, + }, + } + bldr.WithObjects(&comp) + + clnt = bldr.Build() + config, err := GetEffectiveConfig(ctx, clnt, &comp) + Expect(err).ToNot(HaveOccurred()) + + // the propagation policy (here, set in repository) is not inherited + ocmConfig[0].Policy = v1alpha1.ConfigurationPolicyDoNotPropagate + Expect(config).To(Equal(ocmConfig)) + }) + }) + Context("get latest valid component version and regex filter", func() { var ( repo ocmctx.Repository @@ -302,21 +756,21 @@ consumers: env = NewBuilder() env.OCMCommonTransport(CTFPath, accessio.FormatDirectory, func() { - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(Version1, func() { }) }) - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(Version2, func() { }) }) - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(Version3, func() { }) }) }) repo = Must(ctf.Open(env, accessobj.ACC_WRITABLE, CTFPath, vfs.FileMode(vfs.O_RDWR), env)) - c = Must(repo.LookupComponent(Component)) + c = Must(repo.LookupComponent(TestComponent)) }) AfterEach(func() { @@ -352,7 +806,7 @@ consumers: privkey3, pubkey3 := Must2(rsa.CreateKeyPair()) env.OCMCommonTransport(CTFPath, accessio.FormatDirectory, func() { - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(Version1, func() { env.Resource(Resource, Version1, resourcetypes.PLAIN_TEXT, v1.LocalRelation, func() { env.BlobData(mime.MIME_TEXT, []byte("testdata")) @@ -367,7 +821,7 @@ consumers: }) }) repo = Must(ctf.Open(env, accessobj.ACC_WRITABLE, CTFPath, vfs.FileMode(vfs.O_RDWR), env)) - cv = Must(repo.LookupComponentVersion(Component, Version1)) + cv = Must(repo.LookupComponentVersion(TestComponent, Version1)) opts := signing.NewOptions( signing.Resolver(repo), @@ -421,25 +875,25 @@ consumers: v2 := "1.1.0" v3 := "0.9.0" env.OCMCommonTransport(CTFPath, accessio.FormatDirectory, func() { - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(v1, func() { env.Label(v1alpha1.OCMLabelDowngradable, `> 1.0.0`) }) }) - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(v2, func() { }) }) - env.Component(Component, func() { + env.Component(TestComponent, func() { env.Version(v3, func() { }) }) }) repo = Must(ctf.Open(env, accessobj.ACC_WRITABLE, CTFPath, vfs.FileMode(vfs.O_RDWR), env)) - cv1 = Must(repo.LookupComponentVersion(Component, v1)) - cv2 = Must(repo.LookupComponentVersion(Component, v2)) - cv3 = Must(repo.LookupComponentVersion(Component, v3)) + cv1 = Must(repo.LookupComponentVersion(TestComponent, v1)) + cv2 = Must(repo.LookupComponentVersion(TestComponent, v2)) + cv3 = Must(repo.LookupComponentVersion(TestComponent, v3)) }) AfterEach(func() { diff --git a/pkg/ocm/verification.go b/pkg/ocm/verification.go index 731390e1..3620331b 100644 --- a/pkg/ocm/verification.go +++ b/pkg/ocm/verification.go @@ -4,6 +4,12 @@ import ( "context" "encoding/base64" "fmt" + "strings" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/resolvers" + "ocm.software/ocm/api/ocm/tools/signing" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -13,6 +19,13 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" ) +// Verification is an internal representation of v1alpha1.Verification where the public key is already extracted from +// the value or secret. +type Verification struct { + Signature string + PublicKey []byte +} + func GetVerifications(ctx context.Context, client ctrl.Client, obj v1alpha1.VerificationProvider, ) ([]Verification, error) { @@ -51,3 +64,31 @@ func GetVerifications(ctx context.Context, client ctrl.Client, return v, nil } + +func VerifyComponentVersion(ctx context.Context, cv ocm.ComponentVersionAccess, sigs []string) (*Descriptors, error) { + logger := log.FromContext(ctx).WithName("signature-validation") + + if len(sigs) == 0 || cv == nil { + return nil, nil + } + octx := cv.GetContext() + + resolver := resolvers.NewCompoundResolver(cv.Repository(), octx.GetResolver()) + opts := signing.NewOptions( + signing.Resolver(resolver), + // do we really want to verify the digests here? isn't it sufficient to verify the signatures since + // the digest verification can and has to be done anyways by the resource controller? + signing.VerifyDigests(), + signing.VerifySignature(sigs...), + signing.Recursive(), + ) + + ws := signing.DefaultWalkingState(cv.GetContext()) + _, err := signing.Apply(nil, ws, cv, opts) + if err != nil { + return nil, fmt.Errorf("failed to verify component signatures %s: %w", strings.Join(sigs, ", "), err) + } + logger.Info("successfully verified component signature") + + return &Descriptors{List: signing.ListComponentDescriptors(cv, ws)}, nil +} diff --git a/test/e2e/testdata/replication/with-config/Component-intermediate.yaml b/test/e2e/testdata/replication/with-config/Component-intermediate.yaml index 93811f9e..8ed24bc2 100644 --- a/test/e2e/testdata/replication/with-config/Component-intermediate.yaml +++ b/test/e2e/testdata/replication/with-config/Component-intermediate.yaml @@ -9,4 +9,10 @@ spec: repositoryRef: name: intermediate-repository namespace: e2e-replication-controller-test + ocmConfig: + - apiVersion: delivery.ocm.software/v1alpha1 + kind: OCMRepository + name: intermediate-repository + namespace: e2e-replication-controller-test + policy: Propagate semver: 6.6.2 diff --git a/test/e2e/testdata/replication/with-config/Component-origin.yaml b/test/e2e/testdata/replication/with-config/Component-origin.yaml index aa3e8b6b..5f00e374 100644 --- a/test/e2e/testdata/replication/with-config/Component-origin.yaml +++ b/test/e2e/testdata/replication/with-config/Component-origin.yaml @@ -9,4 +9,10 @@ spec: repositoryRef: name: origin-repository namespace: e2e-replication-controller-test + ocmConfig: + - apiVersion: delivery.ocm.software/v1alpha1 + kind: OCMRepository + name: origin-repository + namespace: e2e-replication-controller-test + policy: Propagate semver: 6.6.2 diff --git a/test/e2e/testdata/replication/with-config/OCMRepository-intermediate.yaml b/test/e2e/testdata/replication/with-config/OCMRepository-intermediate.yaml index 31130dfc..7c91ccc1 100644 --- a/test/e2e/testdata/replication/with-config/OCMRepository-intermediate.yaml +++ b/test/e2e/testdata/replication/with-config/OCMRepository-intermediate.yaml @@ -4,8 +4,10 @@ metadata: name: intermediate-repository namespace: e2e-replication-controller-test spec: - configRefs: - - name: creds-intermediate + ocmConfig: + - kind: ConfigMap + name: creds-intermediate + policy: Propagate interval: 2m0s repositorySpec: baseUrl: http://protected-registry1-internal.default.svc.cluster.local:5001 diff --git a/test/e2e/testdata/replication/with-config/OCMRepository-target.yaml b/test/e2e/testdata/replication/with-config/OCMRepository-target.yaml index d1d0ae00..3f9ff440 100644 --- a/test/e2e/testdata/replication/with-config/OCMRepository-target.yaml +++ b/test/e2e/testdata/replication/with-config/OCMRepository-target.yaml @@ -4,8 +4,10 @@ metadata: name: destination-repository namespace: e2e-replication-controller-test spec: - configRefs: - - name: creds-target + ocmConfig: + - kind: ConfigMap + name: creds-target + policy: Propagate interval: 2m0s repositorySpec: baseUrl: http://protected-registry2-internal.default.svc.cluster.local:5002 diff --git a/test/e2e/testdata/replication/with-config/Replication-to-intermediate.yaml b/test/e2e/testdata/replication/with-config/Replication-to-intermediate.yaml index 82bb2cae..ac55b758 100644 --- a/test/e2e/testdata/replication/with-config/Replication-to-intermediate.yaml +++ b/test/e2e/testdata/replication/with-config/Replication-to-intermediate.yaml @@ -7,8 +7,17 @@ spec: componentRef: name: another-podinfo namespace: e2e-replication-controller-test - configRefs: - - name: transfer-options + ocmConfig: + - kind: ConfigMap + name: transfer-options + - apiVersion: delivery.ocm.software/v1alpha1 + kind: OCMRepository + name: intermediate-repository + namespace: e2e-replication-controller-test + - apiVersion: delivery.ocm.software/v1alpha1 + kind: Component + name: another-podinfo + namespace: e2e-replication-controller-test interval: 2m0s targetRepositoryRef: name: intermediate-repository diff --git a/test/e2e/testdata/replication/with-config/Replication-to-target.yaml b/test/e2e/testdata/replication/with-config/Replication-to-target.yaml index efacd180..b3110fe3 100644 --- a/test/e2e/testdata/replication/with-config/Replication-to-target.yaml +++ b/test/e2e/testdata/replication/with-config/Replication-to-target.yaml @@ -7,8 +7,17 @@ spec: componentRef: name: intermediate-component namespace: e2e-replication-controller-test - configRefs: - - name: transfer-options + ocmConfig: + - kind: ConfigMap + name: transfer-options + - apiVersion: delivery.ocm.software/v1alpha1 + kind: OCMRepository + name: destination-repository + namespace: e2e-replication-controller-test + - apiVersion: delivery.ocm.software/v1alpha1 + kind: Component + name: intermediate-component + namespace: e2e-replication-controller-test interval: 2m0s targetRepositoryRef: name: destination-repository