|  | 
|  | 1 | +/* | 
|  | 2 | +Copyright 2025 The Flux authors | 
|  | 3 | +
 | 
|  | 4 | +Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 5 | +you may not use this file except in compliance with the License. | 
|  | 6 | +You may obtain a copy of the License at | 
|  | 7 | +
 | 
|  | 8 | +    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 9 | +
 | 
|  | 10 | +Unless required by applicable law or agreed to in writing, software | 
|  | 11 | +distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 13 | +See the License for the specific language governing permissions and | 
|  | 14 | +limitations under the License. | 
|  | 15 | +*/ | 
|  | 16 | + | 
|  | 17 | +package v1 | 
|  | 18 | + | 
|  | 19 | +import ( | 
|  | 20 | +	"time" | 
|  | 21 | + | 
|  | 22 | +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 
|  | 23 | + | 
|  | 24 | +	"github.com/fluxcd/pkg/apis/meta" | 
|  | 25 | +) | 
|  | 26 | + | 
|  | 27 | +const ( | 
|  | 28 | +	// OCIRepositoryKind is the string representation of an OCIRepository. | 
|  | 29 | +	OCIRepositoryKind = "OCIRepository" | 
|  | 30 | + | 
|  | 31 | +	// OCIRepositoryPrefix is the prefix used for OCIRepository URLs. | 
|  | 32 | +	OCIRepositoryPrefix = "oci://" | 
|  | 33 | + | 
|  | 34 | +	// GenericOCIProvider provides support for authentication using static credentials | 
|  | 35 | +	// for any OCI compatible API such as Docker Registry, GitHub Container Registry, | 
|  | 36 | +	// Docker Hub, Quay, etc. | 
|  | 37 | +	GenericOCIProvider string = "generic" | 
|  | 38 | + | 
|  | 39 | +	// AmazonOCIProvider provides support for OCI authentication using AWS IRSA. | 
|  | 40 | +	AmazonOCIProvider string = "aws" | 
|  | 41 | + | 
|  | 42 | +	// GoogleOCIProvider provides support for OCI authentication using GCP workload identity. | 
|  | 43 | +	GoogleOCIProvider string = "gcp" | 
|  | 44 | + | 
|  | 45 | +	// AzureOCIProvider provides support for OCI authentication using a Azure Service Principal, | 
|  | 46 | +	// Managed Identity or Shared Key. | 
|  | 47 | +	AzureOCIProvider string = "azure" | 
|  | 48 | + | 
|  | 49 | +	// OCILayerExtract defines the operation type for extracting the content from an OCI artifact layer. | 
|  | 50 | +	OCILayerExtract = "extract" | 
|  | 51 | + | 
|  | 52 | +	// OCILayerCopy defines the operation type for copying the content from an OCI artifact layer. | 
|  | 53 | +	OCILayerCopy = "copy" | 
|  | 54 | +) | 
|  | 55 | + | 
|  | 56 | +// OCIRepositorySpec defines the desired state of OCIRepository | 
|  | 57 | +type OCIRepositorySpec struct { | 
|  | 58 | +	// URL is a reference to an OCI artifact repository hosted | 
|  | 59 | +	// on a remote container registry. | 
|  | 60 | +	// +kubebuilder:validation:Pattern="^oci://.*$" | 
|  | 61 | +	// +required | 
|  | 62 | +	URL string `json:"url"` | 
|  | 63 | + | 
|  | 64 | +	// The OCI reference to pull and monitor for changes, | 
|  | 65 | +	// defaults to the latest tag. | 
|  | 66 | +	// +optional | 
|  | 67 | +	Reference *OCIRepositoryRef `json:"ref,omitempty"` | 
|  | 68 | + | 
|  | 69 | +	// LayerSelector specifies which layer should be extracted from the OCI artifact. | 
|  | 70 | +	// When not specified, the first layer found in the artifact is selected. | 
|  | 71 | +	// +optional | 
|  | 72 | +	LayerSelector *OCILayerSelector `json:"layerSelector,omitempty"` | 
|  | 73 | + | 
|  | 74 | +	// The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. | 
|  | 75 | +	// When not specified, defaults to 'generic'. | 
|  | 76 | +	// +kubebuilder:validation:Enum=generic;aws;azure;gcp | 
|  | 77 | +	// +kubebuilder:default:=generic | 
|  | 78 | +	// +optional | 
|  | 79 | +	Provider string `json:"provider,omitempty"` | 
|  | 80 | + | 
|  | 81 | +	// SecretRef contains the secret name containing the registry login | 
|  | 82 | +	// credentials to resolve image metadata. | 
|  | 83 | +	// The secret must be of type kubernetes.io/dockerconfigjson. | 
|  | 84 | +	// +optional | 
|  | 85 | +	SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` | 
|  | 86 | + | 
|  | 87 | +	// Verify contains the secret name containing the trusted public keys | 
|  | 88 | +	// used to verify the signature and specifies which provider to use to check | 
|  | 89 | +	// whether OCI image is authentic. | 
|  | 90 | +	// +optional | 
|  | 91 | +	Verify *OCIRepositoryVerification `json:"verify,omitempty"` | 
|  | 92 | + | 
|  | 93 | +	// ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate | 
|  | 94 | +	// the image pull if the service account has attached pull secrets. For more information: | 
|  | 95 | +	// https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account | 
|  | 96 | +	// +optional | 
|  | 97 | +	ServiceAccountName string `json:"serviceAccountName,omitempty"` | 
|  | 98 | + | 
|  | 99 | +	// CertSecretRef can be given the name of a Secret containing | 
|  | 100 | +	// either or both of | 
|  | 101 | +	// | 
|  | 102 | +	// - a PEM-encoded client certificate (`tls.crt`) and private | 
|  | 103 | +	// key (`tls.key`); | 
|  | 104 | +	// - a PEM-encoded CA certificate (`ca.crt`) | 
|  | 105 | +	// | 
|  | 106 | +	// and whichever are supplied, will be used for connecting to the | 
|  | 107 | +	// registry. The client cert and key are useful if you are | 
|  | 108 | +	// authenticating with a certificate; the CA cert is useful if | 
|  | 109 | +	// you are using a self-signed server certificate. The Secret must | 
|  | 110 | +	// be of type `Opaque` or `kubernetes.io/tls`. | 
|  | 111 | +	// | 
|  | 112 | +	// Note: Support for the `caFile`, `certFile` and `keyFile` keys have | 
|  | 113 | +	// been deprecated. | 
|  | 114 | +	// +optional | 
|  | 115 | +	CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"` | 
|  | 116 | + | 
|  | 117 | +	// ProxySecretRef specifies the Secret containing the proxy configuration | 
|  | 118 | +	// to use while communicating with the container registry. | 
|  | 119 | +	// +optional | 
|  | 120 | +	ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"` | 
|  | 121 | + | 
|  | 122 | +	// Interval at which the OCIRepository URL is checked for updates. | 
|  | 123 | +	// This interval is approximate and may be subject to jitter to ensure | 
|  | 124 | +	// efficient use of resources. | 
|  | 125 | +	// +kubebuilder:validation:Type=string | 
|  | 126 | +	// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" | 
|  | 127 | +	// +required | 
|  | 128 | +	Interval metav1.Duration `json:"interval"` | 
|  | 129 | + | 
|  | 130 | +	// The timeout for remote OCI Repository operations like pulling, defaults to 60s. | 
|  | 131 | +	// +kubebuilder:default="60s" | 
|  | 132 | +	// +kubebuilder:validation:Type=string | 
|  | 133 | +	// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m))+$" | 
|  | 134 | +	// +optional | 
|  | 135 | +	Timeout *metav1.Duration `json:"timeout,omitempty"` | 
|  | 136 | + | 
|  | 137 | +	// Ignore overrides the set of excluded patterns in the .sourceignore format | 
|  | 138 | +	// (which is the same as .gitignore). If not provided, a default will be used, | 
|  | 139 | +	// consult the documentation for your version to find out what those are. | 
|  | 140 | +	// +optional | 
|  | 141 | +	Ignore *string `json:"ignore,omitempty"` | 
|  | 142 | + | 
|  | 143 | +	// Insecure allows connecting to a non-TLS HTTP container registry. | 
|  | 144 | +	// +optional | 
|  | 145 | +	Insecure bool `json:"insecure,omitempty"` | 
|  | 146 | + | 
|  | 147 | +	// This flag tells the controller to suspend the reconciliation of this source. | 
|  | 148 | +	// +optional | 
|  | 149 | +	Suspend bool `json:"suspend,omitempty"` | 
|  | 150 | +} | 
|  | 151 | + | 
|  | 152 | +// OCIRepositoryRef defines the image reference for the OCIRepository's URL | 
|  | 153 | +type OCIRepositoryRef struct { | 
|  | 154 | +	// Digest is the image digest to pull, takes precedence over SemVer. | 
|  | 155 | +	// The value should be in the format 'sha256:<HASH>'. | 
|  | 156 | +	// +optional | 
|  | 157 | +	Digest string `json:"digest,omitempty"` | 
|  | 158 | + | 
|  | 159 | +	// SemVer is the range of tags to pull selecting the latest within | 
|  | 160 | +	// the range, takes precedence over Tag. | 
|  | 161 | +	// +optional | 
|  | 162 | +	SemVer string `json:"semver,omitempty"` | 
|  | 163 | + | 
|  | 164 | +	// SemverFilter is a regex pattern to filter the tags within the SemVer range. | 
|  | 165 | +	// +optional | 
|  | 166 | +	SemverFilter string `json:"semverFilter,omitempty"` | 
|  | 167 | + | 
|  | 168 | +	// Tag is the image tag to pull, defaults to latest. | 
|  | 169 | +	// +optional | 
|  | 170 | +	Tag string `json:"tag,omitempty"` | 
|  | 171 | +} | 
|  | 172 | + | 
|  | 173 | +// OCILayerSelector specifies which layer should be extracted from an OCI Artifact | 
|  | 174 | +type OCILayerSelector struct { | 
|  | 175 | +	// MediaType specifies the OCI media type of the layer | 
|  | 176 | +	// which should be extracted from the OCI Artifact. The | 
|  | 177 | +	// first layer matching this type is selected. | 
|  | 178 | +	// +optional | 
|  | 179 | +	MediaType string `json:"mediaType,omitempty"` | 
|  | 180 | + | 
|  | 181 | +	// Operation specifies how the selected layer should be processed. | 
|  | 182 | +	// By default, the layer compressed content is extracted to storage. | 
|  | 183 | +	// When the operation is set to 'copy', the layer compressed content | 
|  | 184 | +	// is persisted to storage as it is. | 
|  | 185 | +	// +kubebuilder:validation:Enum=extract;copy | 
|  | 186 | +	// +optional | 
|  | 187 | +	Operation string `json:"operation,omitempty"` | 
|  | 188 | +} | 
|  | 189 | + | 
|  | 190 | +// OCIRepositoryStatus defines the observed state of OCIRepository | 
|  | 191 | +type OCIRepositoryStatus struct { | 
|  | 192 | +	// ObservedGeneration is the last observed generation. | 
|  | 193 | +	// +optional | 
|  | 194 | +	ObservedGeneration int64 `json:"observedGeneration,omitempty"` | 
|  | 195 | + | 
|  | 196 | +	// Conditions holds the conditions for the OCIRepository. | 
|  | 197 | +	// +optional | 
|  | 198 | +	Conditions []metav1.Condition `json:"conditions,omitempty"` | 
|  | 199 | + | 
|  | 200 | +	// URL is the download link for the artifact output of the last OCI Repository sync. | 
|  | 201 | +	// +optional | 
|  | 202 | +	URL string `json:"url,omitempty"` | 
|  | 203 | + | 
|  | 204 | +	// Artifact represents the output of the last successful OCI Repository sync. | 
|  | 205 | +	// +optional | 
|  | 206 | +	Artifact *Artifact `json:"artifact,omitempty"` | 
|  | 207 | + | 
|  | 208 | +	// ContentConfigChecksum is a checksum of all the configurations related to | 
|  | 209 | +	// the content of the source artifact: | 
|  | 210 | +	//  - .spec.ignore | 
|  | 211 | +	//  - .spec.layerSelector | 
|  | 212 | +	// observed in .status.observedGeneration version of the object. This can | 
|  | 213 | +	// be used to determine if the content configuration has changed and the | 
|  | 214 | +	// artifact needs to be rebuilt. | 
|  | 215 | +	// It has the format of `<algo>:<checksum>`, for example: `sha256:<checksum>`. | 
|  | 216 | +	// | 
|  | 217 | +	// Deprecated: Replaced with explicit fields for observed artifact content | 
|  | 218 | +	// config in the status. | 
|  | 219 | +	// +optional | 
|  | 220 | +	ContentConfigChecksum string `json:"contentConfigChecksum,omitempty"` | 
|  | 221 | + | 
|  | 222 | +	// ObservedIgnore is the observed exclusion patterns used for constructing | 
|  | 223 | +	// the source artifact. | 
|  | 224 | +	// +optional | 
|  | 225 | +	ObservedIgnore *string `json:"observedIgnore,omitempty"` | 
|  | 226 | + | 
|  | 227 | +	// ObservedLayerSelector is the observed layer selector used for constructing | 
|  | 228 | +	// the source artifact. | 
|  | 229 | +	// +optional | 
|  | 230 | +	ObservedLayerSelector *OCILayerSelector `json:"observedLayerSelector,omitempty"` | 
|  | 231 | + | 
|  | 232 | +	meta.ReconcileRequestStatus `json:",inline"` | 
|  | 233 | +} | 
|  | 234 | + | 
|  | 235 | +const ( | 
|  | 236 | +	// OCIPullFailedReason signals that a pull operation failed. | 
|  | 237 | +	OCIPullFailedReason string = "OCIArtifactPullFailed" | 
|  | 238 | + | 
|  | 239 | +	// OCILayerOperationFailedReason signals that an OCI layer operation failed. | 
|  | 240 | +	OCILayerOperationFailedReason string = "OCIArtifactLayerOperationFailed" | 
|  | 241 | +) | 
|  | 242 | + | 
|  | 243 | +// GetConditions returns the status conditions of the object. | 
|  | 244 | +func (in OCIRepository) GetConditions() []metav1.Condition { | 
|  | 245 | +	return in.Status.Conditions | 
|  | 246 | +} | 
|  | 247 | + | 
|  | 248 | +// SetConditions sets the status conditions on the object. | 
|  | 249 | +func (in *OCIRepository) SetConditions(conditions []metav1.Condition) { | 
|  | 250 | +	in.Status.Conditions = conditions | 
|  | 251 | +} | 
|  | 252 | + | 
|  | 253 | +// GetRequeueAfter returns the duration after which the OCIRepository must be | 
|  | 254 | +// reconciled again. | 
|  | 255 | +func (in OCIRepository) GetRequeueAfter() time.Duration { | 
|  | 256 | +	return in.Spec.Interval.Duration | 
|  | 257 | +} | 
|  | 258 | + | 
|  | 259 | +// GetArtifact returns the latest Artifact from the OCIRepository if present in | 
|  | 260 | +// the status sub-resource. | 
|  | 261 | +func (in *OCIRepository) GetArtifact() *Artifact { | 
|  | 262 | +	return in.Status.Artifact | 
|  | 263 | +} | 
|  | 264 | + | 
|  | 265 | +// GetLayerMediaType returns the media type layer selector if found in spec. | 
|  | 266 | +func (in *OCIRepository) GetLayerMediaType() string { | 
|  | 267 | +	if in.Spec.LayerSelector == nil { | 
|  | 268 | +		return "" | 
|  | 269 | +	} | 
|  | 270 | + | 
|  | 271 | +	return in.Spec.LayerSelector.MediaType | 
|  | 272 | +} | 
|  | 273 | + | 
|  | 274 | +// GetLayerOperation returns the layer selector operation (defaults to extract). | 
|  | 275 | +func (in *OCIRepository) GetLayerOperation() string { | 
|  | 276 | +	if in.Spec.LayerSelector == nil || in.Spec.LayerSelector.Operation == "" { | 
|  | 277 | +		return OCILayerExtract | 
|  | 278 | +	} | 
|  | 279 | + | 
|  | 280 | +	return in.Spec.LayerSelector.Operation | 
|  | 281 | +} | 
|  | 282 | + | 
|  | 283 | +// +genclient | 
|  | 284 | +// +kubebuilder:storageversion | 
|  | 285 | +// +kubebuilder:object:root=true | 
|  | 286 | +// +kubebuilder:resource:shortName=ocirepo | 
|  | 287 | +// +kubebuilder:subresource:status | 
|  | 288 | +// +kubebuilder:printcolumn:name="URL",type=string,JSONPath=`.spec.url` | 
|  | 289 | +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" | 
|  | 290 | +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" | 
|  | 291 | +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" | 
|  | 292 | + | 
|  | 293 | +// OCIRepository is the Schema for the ocirepositories API | 
|  | 294 | +type OCIRepository struct { | 
|  | 295 | +	metav1.TypeMeta   `json:",inline"` | 
|  | 296 | +	metav1.ObjectMeta `json:"metadata,omitempty"` | 
|  | 297 | + | 
|  | 298 | +	Spec OCIRepositorySpec `json:"spec,omitempty"` | 
|  | 299 | +	// +kubebuilder:default={"observedGeneration":-1} | 
|  | 300 | +	Status OCIRepositoryStatus `json:"status,omitempty"` | 
|  | 301 | +} | 
|  | 302 | + | 
|  | 303 | +// OCIRepositoryList contains a list of OCIRepository | 
|  | 304 | +// +kubebuilder:object:root=true | 
|  | 305 | +type OCIRepositoryList struct { | 
|  | 306 | +	metav1.TypeMeta `json:",inline"` | 
|  | 307 | +	metav1.ListMeta `json:"metadata,omitempty"` | 
|  | 308 | +	Items           []OCIRepository `json:"items"` | 
|  | 309 | +} | 
|  | 310 | + | 
|  | 311 | +func init() { | 
|  | 312 | +	SchemeBuilder.Register(&OCIRepository{}, &OCIRepositoryList{}) | 
|  | 313 | +} | 
0 commit comments