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