Skip to content

Commit 1ec2713

Browse files
committed
Add support for using an OCI image as source
This change defines an OCIRepository CRD that allows a user to specify a given image to use as a source. The contents of the image (including images with multiple layers) are converted into a TAR and exposed to consumers following the same conventions as the other source types. apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: OCIRepository metadata: name: podinfo-deploy namespace: flux-system spec: interval: 10m url: ghcr.io/stefanprodan/podinfo-deploy ref: # one of tag: "latest" digest: "sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2" semver: "1.x" auth: # one of secretRef: name: regcred serviceAccountName: reg In addition to the url, interval, timeout, ignore, and suspend keys (all of which behave consistently with the existing source types) this CRD also defines authentication via both an image pull Secret and a service serviceAccountName which provide ways to contribute registry connection credentials for the specified image. This change also adds a new way to write to the storage archive by streaming data from an incoming TAR without writing it to the filesystem. A couple of code and test functions were extracted to reuse common functionality for both archive strategies. Signed-off-by: Ben Hale <[email protected]>
1 parent f3f7193 commit 1ec2713

17 files changed

+1547
-106
lines changed

.github/workflows/e2e.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
kubectl -n source-system wait gitrepository/gitrepository-sample --for=condition=ready --timeout=1m
5757
kubectl -n source-system wait helmrepository/helmrepository-sample --for=condition=ready --timeout=1m
5858
kubectl -n source-system wait helmchart/helmchart-sample --for=condition=ready --timeout=1m
59+
kubectl -n source-system wait ocirepository/ocirepository-sample --for=condition=ready --timeout=1m
5960
kubectl -n source-system delete -f ./config/samples
6061
- name: Run HelmChart values file tests
6162
run: |
@@ -113,6 +114,7 @@ jobs:
113114
kubectl -n source-system get gitrepositories -oyaml
114115
kubectl -n source-system get helmrepositories -oyaml
115116
kubectl -n source-system get helmcharts -oyaml
117+
kubectl -n source-system get ocirepositories -oyaml
116118
kubectl -n source-system get all
117119
kubectl -n source-system logs deploy/source-controller
118120
kubectl -n minio get all

PROJECT

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ resources:
1313
- group: source
1414
kind: Bucket
1515
version: v1beta1
16+
- group: source
17+
kind: OCIRepository
18+
version: v1beta1
1619
version: "2"

api/v1beta1/ocirepository_types.go

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
Copyright 2020 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 v1beta1
18+
19+
import (
20+
"github.com/fluxcd/pkg/apis/meta"
21+
apimeta "k8s.io/apimachinery/pkg/api/meta"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
)
24+
25+
const (
26+
// OCIRepositoryKind is the string representation of a OCIRepository.
27+
OCIRepositoryKind = "OCIRepository"
28+
)
29+
30+
// OCIRepositorySpec defines the desired state of OCIRepository
31+
type OCIRepositorySpec struct {
32+
33+
// URL is a reference to an image in a remote registry
34+
// +required
35+
URL string `json:"url"`
36+
37+
// The OCI reference to pull and monitor for changes, defaults to
38+
// latest tag.
39+
// +optional
40+
Reference *OCIRepositoryRef `json:"ref,omitempty"`
41+
42+
// The credentials to use to pull and monitor for changes, defaults
43+
// to anonymous access.
44+
// +optional
45+
Authentication *OCIRepositoryAuth `json:"auth,omitempty"`
46+
47+
// The interval at which to check for image updates.
48+
// +required
49+
Interval metav1.Duration `json:"interval"`
50+
51+
// The timeout for remote OCI Repository operations like pulling, defaults to 20s.
52+
// +kubebuilder:default="20s"
53+
// +optional
54+
Timeout *metav1.Duration `json:"timeout,omitempty"`
55+
56+
// Ignore overrides the set of excluded patterns in the .sourceignore format
57+
// (which is the same as .gitignore). If not provided, a default will be used,
58+
// consult the documentation for your version to find out what those are.
59+
// +optional
60+
Ignore *string `json:"ignore,omitempty"`
61+
62+
// This flag tells the controller to suspend the reconciliation of this source.
63+
// +optional
64+
Suspend bool `json:"suspend,omitempty"`
65+
}
66+
67+
// OCIRepositoryRef defines the image reference for the OCIRepository's URL
68+
type OCIRepositoryRef struct {
69+
70+
// Digest is the image digest to pull, takes precedence over SemVer.
71+
// Value should be in the form sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
72+
// +optional
73+
Digest string `json:"digest,omitempty"`
74+
75+
// SemVer is the range of tags to pull selecting the latest within
76+
// the range, takes precedence over Tag.
77+
// +optional
78+
SemVer string `json:"semver,omitempty"`
79+
80+
// Tag is the image tag to pull, defaults to latest.
81+
// +kubebuilder:default:=latest
82+
// +optional
83+
Tag string `json:"tag,omitempty"`
84+
}
85+
86+
// OCIRepositoryAuth defines the desired authentication mechanism of OCIRepository
87+
type OCIRepositoryAuth struct {
88+
89+
// SecretRef contains the secret name containing the registry login
90+
// credentials to resolve image metadata.
91+
// The secret must be of type kubernetes.io/dockerconfigjson.
92+
// +optional
93+
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
94+
95+
// ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate
96+
// the image pull if the service account has attached pull secrets. For more information:
97+
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account
98+
// +optional
99+
ServiceAccountName string `json:"serviceAccountName,omitempty"`
100+
}
101+
102+
// OCIRepositoryStatus defines the observed state of OCIRepository
103+
type OCIRepositoryStatus struct {
104+
// ObservedGeneration is the last observed generation.
105+
// +optional
106+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
107+
108+
// Conditions holds the conditions for the OCIRepository.
109+
// +optional
110+
Conditions []metav1.Condition `json:"conditions,omitempty"`
111+
112+
// URL is the download link for the artifact output of the last
113+
//image sync.
114+
// +optional
115+
URL string `json:"url,omitempty"`
116+
117+
// Artifact represents the output of the last successful image sync.
118+
// +optional
119+
Artifact *Artifact `json:"artifact,omitempty"`
120+
121+
meta.ReconcileRequestStatus `json:",inline"`
122+
}
123+
124+
const (
125+
// OCIRepositoryOperationSucceedReason represents the fact that the
126+
// image pull operation succeeded.
127+
OCIRepositoryOperationSucceedReason string = "OCIRepositoryOperationSucceed"
128+
129+
// OCIRepositoryOperationFailedReason represents the fact that the
130+
// image pull operation failed.
131+
OCIRepositoryOperationFailedReason string = "OCIRepositoryOperationFailed"
132+
)
133+
134+
// OCIRepositoryProgressing resets the conditions of the OCIRepository
135+
// to metav1.Condition of type meta.ReadyCondition with status 'Unknown'
136+
// and meta.ProgressingReason reason and message. It returns the
137+
// modified OCCIRepository.
138+
func OCIRepositoryProgressing(repository OCIRepository) OCIRepository {
139+
repository.Status.ObservedGeneration = repository.Generation
140+
repository.Status.URL = ""
141+
repository.Status.Conditions = []metav1.Condition{}
142+
meta.SetResourceCondition(&repository, meta.ReadyCondition, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress")
143+
return repository
144+
}
145+
146+
// OCIRepositoryReady sets the given Artifact and URL on the
147+
// OCIRepository and sets the meta.ReadyCondition to 'True', with the
148+
// given reason and message. It returns the modified OCIRepository.
149+
func OCIRepositoryReady(repository OCIRepository, artifact Artifact, url, reason, message string) OCIRepository {
150+
repository.Status.Artifact = &artifact
151+
repository.Status.URL = url
152+
meta.SetResourceCondition(&repository, meta.ReadyCondition, metav1.ConditionTrue, reason, message)
153+
return repository
154+
}
155+
156+
// OCIRepositoryNotReady sets the meta.ReadyCondition on the given
157+
// OCIRepository to 'False', with the given reason and message. It
158+
// returns the modified OCIRepository.
159+
func OCIRepositoryNotReady(repository OCIRepository, reason, message string) OCIRepository {
160+
meta.SetResourceCondition(&repository, meta.ReadyCondition, metav1.ConditionFalse, reason, message)
161+
return repository
162+
}
163+
164+
// OCIRepositoryReadyMessage returns the message of the
165+
// metav1.Condition of type meta.ReadyCondition with status 'True' if
166+
// present, or an empty string.
167+
func OCIRepositoryReadyMessage(repository OCIRepository) string {
168+
if c := apimeta.FindStatusCondition(repository.Status.Conditions, meta.ReadyCondition); c != nil {
169+
if c.Status == metav1.ConditionTrue {
170+
return c.Message
171+
}
172+
}
173+
return ""
174+
}
175+
176+
// GetArtifact returns the latest artifact from the source if present in the
177+
// status sub-resource.
178+
func (in *OCIRepository) GetArtifact() *Artifact {
179+
return in.Status.Artifact
180+
}
181+
182+
// GetStatusConditions returns a pointer to the Status.Conditions slice
183+
func (in *OCIRepository) GetStatusConditions() *[]metav1.Condition {
184+
return &in.Status.Conditions
185+
}
186+
187+
// GetInterval returns the interval at which the source is updated.
188+
func (in *OCIRepository) GetInterval() metav1.Duration {
189+
return in.Spec.Interval
190+
}
191+
192+
// +genclient
193+
// +genclient:Namespaced
194+
//+kubebuilder:object:root=true
195+
//+kubebuilder:subresource:status
196+
//+kubebuilder:printcolumn:name="URL",type=string,JSONPath=`.spec.url`
197+
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
198+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
199+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
200+
201+
// OCIRepository is the Schema for the ocirepositories API
202+
type OCIRepository struct {
203+
metav1.TypeMeta `json:",inline"`
204+
metav1.ObjectMeta `json:"metadata,omitempty"`
205+
206+
Spec OCIRepositorySpec `json:"spec,omitempty"`
207+
Status OCIRepositoryStatus `json:"status,omitempty"`
208+
}
209+
210+
//+kubebuilder:object:root=true
211+
212+
// OCIRepositoryList contains a list of OCIRepository
213+
type OCIRepositoryList struct {
214+
metav1.TypeMeta `json:",inline"`
215+
metav1.ListMeta `json:"metadata,omitempty"`
216+
Items []OCIRepository `json:"items"`
217+
}
218+
219+
func init() {
220+
SchemeBuilder.Register(&OCIRepository{}, &OCIRepositoryList{})
221+
}

0 commit comments

Comments
 (0)