Skip to content

Commit e98b89a

Browse files
authored
Merge pull request #1794 from fluxcd/ocirepository-v1
Promote OCIRepository API to v1 (GA)
2 parents 220044d + aadaf1c commit e98b89a

29 files changed

+3128
-372
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ manifests: controller-gen ## Generate manifests, e.g. CRD, RBAC, etc.
115115
cd api; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="../config/crd/bases"
116116

117117
api-docs: gen-crd-api-reference-docs ## Generate API reference documentation
118-
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1beta2 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v1beta2/source.md
119118
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v1/source.md
120119

121120
tidy: ## Run go mod tidy

PROJECT

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,7 @@ resources:
4040
- group: source
4141
kind: Bucket
4242
version: v1
43+
- group: source
44+
kind: OCIRepository
45+
version: v1
4346
version: "2"

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ and is a core component of the [GitOps toolkit](https://fluxcd.io/flux/component
1616

1717
## APIs
1818

19-
| Kind | API Version |
20-
|-------------------------------------------------------|------------------------------------|
21-
| [GitRepository](docs/spec/v1/gitrepositories.md) | `source.toolkit.fluxcd.io/v1` |
22-
| [OCIRepository](docs/spec/v1beta2/ocirepositories.md) | `source.toolkit.fluxcd.io/v1beta2` |
23-
| [HelmRepository](docs/spec/v1/helmrepositories.md) | `source.toolkit.fluxcd.io/v1` |
24-
| [HelmChart](docs/spec/v1/helmcharts.md) | `source.toolkit.fluxcd.io/v1` |
25-
| [Bucket](docs/spec/v1/buckets.md) | `source.toolkit.fluxcd.io/v1` |
19+
| Kind | API Version |
20+
|----------------------------------------------------|-------------------------------|
21+
| [GitRepository](docs/spec/v1/gitrepositories.md) | `source.toolkit.fluxcd.io/v1` |
22+
| [OCIRepository](docs/spec/v1/ocirepositories.md) | `source.toolkit.fluxcd.io/v1` |
23+
| [HelmRepository](docs/spec/v1/helmrepositories.md) | `source.toolkit.fluxcd.io/v1` |
24+
| [HelmChart](docs/spec/v1/helmcharts.md) | `source.toolkit.fluxcd.io/v1` |
25+
| [Bucket](docs/spec/v1/buckets.md) | `source.toolkit.fluxcd.io/v1` |
2626

2727
## Features
2828

api/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ require (
2222
github.com/modern-go/reflect2 v1.0.2 // indirect
2323
github.com/spf13/pflag v1.0.6 // indirect
2424
github.com/x448/float16 v0.8.4 // indirect
25-
golang.org/x/net v0.39.0 // indirect
26-
golang.org/x/text v0.24.0 // indirect
25+
golang.org/x/net v0.40.0 // indirect
26+
golang.org/x/text v0.25.0 // indirect
2727
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2828
gopkg.in/inf.v0 v0.9.1 // indirect
2929
k8s.io/klog/v2 v2.130.1 // indirect

api/go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
6565
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
6666
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
6767
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
68-
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
69-
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
68+
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
69+
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
7070
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7171
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7272
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7373
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
7474
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7575
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
76-
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
77-
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
76+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
77+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
7878
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
7979
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
80-
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
81-
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
80+
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
81+
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
8282
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
8383
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
8484
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=

api/v1/ocirepository_types.go

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
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+
// +optional
112+
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
113+
114+
// ProxySecretRef specifies the Secret containing the proxy configuration
115+
// to use while communicating with the container registry.
116+
// +optional
117+
ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"`
118+
119+
// Interval at which the OCIRepository URL is checked for updates.
120+
// This interval is approximate and may be subject to jitter to ensure
121+
// efficient use of resources.
122+
// +kubebuilder:validation:Type=string
123+
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
124+
// +required
125+
Interval metav1.Duration `json:"interval"`
126+
127+
// The timeout for remote OCI Repository operations like pulling, defaults to 60s.
128+
// +kubebuilder:default="60s"
129+
// +kubebuilder:validation:Type=string
130+
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m))+$"
131+
// +optional
132+
Timeout *metav1.Duration `json:"timeout,omitempty"`
133+
134+
// Ignore overrides the set of excluded patterns in the .sourceignore format
135+
// (which is the same as .gitignore). If not provided, a default will be used,
136+
// consult the documentation for your version to find out what those are.
137+
// +optional
138+
Ignore *string `json:"ignore,omitempty"`
139+
140+
// Insecure allows connecting to a non-TLS HTTP container registry.
141+
// +optional
142+
Insecure bool `json:"insecure,omitempty"`
143+
144+
// This flag tells the controller to suspend the reconciliation of this source.
145+
// +optional
146+
Suspend bool `json:"suspend,omitempty"`
147+
}
148+
149+
// OCIRepositoryRef defines the image reference for the OCIRepository's URL
150+
type OCIRepositoryRef struct {
151+
// Digest is the image digest to pull, takes precedence over SemVer.
152+
// The value should be in the format 'sha256:<HASH>'.
153+
// +optional
154+
Digest string `json:"digest,omitempty"`
155+
156+
// SemVer is the range of tags to pull selecting the latest within
157+
// the range, takes precedence over Tag.
158+
// +optional
159+
SemVer string `json:"semver,omitempty"`
160+
161+
// SemverFilter is a regex pattern to filter the tags within the SemVer range.
162+
// +optional
163+
SemverFilter string `json:"semverFilter,omitempty"`
164+
165+
// Tag is the image tag to pull, defaults to latest.
166+
// +optional
167+
Tag string `json:"tag,omitempty"`
168+
}
169+
170+
// OCILayerSelector specifies which layer should be extracted from an OCI Artifact
171+
type OCILayerSelector struct {
172+
// MediaType specifies the OCI media type of the layer
173+
// which should be extracted from the OCI Artifact. The
174+
// first layer matching this type is selected.
175+
// +optional
176+
MediaType string `json:"mediaType,omitempty"`
177+
178+
// Operation specifies how the selected layer should be processed.
179+
// By default, the layer compressed content is extracted to storage.
180+
// When the operation is set to 'copy', the layer compressed content
181+
// is persisted to storage as it is.
182+
// +kubebuilder:validation:Enum=extract;copy
183+
// +optional
184+
Operation string `json:"operation,omitempty"`
185+
}
186+
187+
// OCIRepositoryStatus defines the observed state of OCIRepository
188+
type OCIRepositoryStatus struct {
189+
// ObservedGeneration is the last observed generation.
190+
// +optional
191+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
192+
193+
// Conditions holds the conditions for the OCIRepository.
194+
// +optional
195+
Conditions []metav1.Condition `json:"conditions,omitempty"`
196+
197+
// URL is the download link for the artifact output of the last OCI Repository sync.
198+
// +optional
199+
URL string `json:"url,omitempty"`
200+
201+
// Artifact represents the output of the last successful OCI Repository sync.
202+
// +optional
203+
Artifact *Artifact `json:"artifact,omitempty"`
204+
205+
// ObservedIgnore is the observed exclusion patterns used for constructing
206+
// the source artifact.
207+
// +optional
208+
ObservedIgnore *string `json:"observedIgnore,omitempty"`
209+
210+
// ObservedLayerSelector is the observed layer selector used for constructing
211+
// the source artifact.
212+
// +optional
213+
ObservedLayerSelector *OCILayerSelector `json:"observedLayerSelector,omitempty"`
214+
215+
meta.ReconcileRequestStatus `json:",inline"`
216+
}
217+
218+
const (
219+
// OCIPullFailedReason signals that a pull operation failed.
220+
OCIPullFailedReason string = "OCIArtifactPullFailed"
221+
222+
// OCILayerOperationFailedReason signals that an OCI layer operation failed.
223+
OCILayerOperationFailedReason string = "OCIArtifactLayerOperationFailed"
224+
)
225+
226+
// GetConditions returns the status conditions of the object.
227+
func (in OCIRepository) GetConditions() []metav1.Condition {
228+
return in.Status.Conditions
229+
}
230+
231+
// SetConditions sets the status conditions on the object.
232+
func (in *OCIRepository) SetConditions(conditions []metav1.Condition) {
233+
in.Status.Conditions = conditions
234+
}
235+
236+
// GetRequeueAfter returns the duration after which the OCIRepository must be
237+
// reconciled again.
238+
func (in OCIRepository) GetRequeueAfter() time.Duration {
239+
return in.Spec.Interval.Duration
240+
}
241+
242+
// GetArtifact returns the latest Artifact from the OCIRepository if present in
243+
// the status sub-resource.
244+
func (in *OCIRepository) GetArtifact() *Artifact {
245+
return in.Status.Artifact
246+
}
247+
248+
// GetLayerMediaType returns the media type layer selector if found in spec.
249+
func (in *OCIRepository) GetLayerMediaType() string {
250+
if in.Spec.LayerSelector == nil {
251+
return ""
252+
}
253+
254+
return in.Spec.LayerSelector.MediaType
255+
}
256+
257+
// GetLayerOperation returns the layer selector operation (defaults to extract).
258+
func (in *OCIRepository) GetLayerOperation() string {
259+
if in.Spec.LayerSelector == nil || in.Spec.LayerSelector.Operation == "" {
260+
return OCILayerExtract
261+
}
262+
263+
return in.Spec.LayerSelector.Operation
264+
}
265+
266+
// +genclient
267+
// +kubebuilder:storageversion
268+
// +kubebuilder:object:root=true
269+
// +kubebuilder:resource:shortName=ocirepo
270+
// +kubebuilder:subresource:status
271+
// +kubebuilder:printcolumn:name="URL",type=string,JSONPath=`.spec.url`
272+
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
273+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
274+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
275+
276+
// OCIRepository is the Schema for the ocirepositories API
277+
type OCIRepository struct {
278+
metav1.TypeMeta `json:",inline"`
279+
metav1.ObjectMeta `json:"metadata,omitempty"`
280+
281+
Spec OCIRepositorySpec `json:"spec,omitempty"`
282+
// +kubebuilder:default={"observedGeneration":-1}
283+
Status OCIRepositoryStatus `json:"status,omitempty"`
284+
}
285+
286+
// OCIRepositoryList contains a list of OCIRepository
287+
// +kubebuilder:object:root=true
288+
type OCIRepositoryList struct {
289+
metav1.TypeMeta `json:",inline"`
290+
metav1.ListMeta `json:"metadata,omitempty"`
291+
Items []OCIRepository `json:"items"`
292+
}
293+
294+
func init() {
295+
SchemeBuilder.Register(&OCIRepository{}, &OCIRepositoryList{})
296+
}

0 commit comments

Comments
 (0)