Skip to content

Commit ce07344

Browse files
authored
Merge pull request #146 from fluxcd/s3-bucket-controller
Implement S3 bucket source
2 parents 4f30ff1 + 6f8c381 commit ce07344

23 files changed

+1544
-8
lines changed

.github/workflows/e2e.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ jobs:
2929
uses: fluxcd/pkg/actions/kustomize@master
3030
- name: Setup Kubebuilder
3131
uses: fluxcd/pkg/actions/kubebuilder@master
32+
- name: Setup Helm
33+
uses: fluxcd/pkg/actions/helm@master
3234
- name: Run tests
3335
run: make test
3436
env:
@@ -64,6 +66,27 @@ jobs:
6466
kubectl -n source-system wait helmchart/mariadb --for=condition=ready --timeout=5m
6567
kubectl -n source-system wait helmchart/mariadb-git --for=condition=ready --timeout=5m
6668
kubectl -n source-system delete -f ./config/testdata/helmchart-valuesfile
69+
- name: Setup Minio
70+
run: |
71+
kubectl create ns minio
72+
helm repo add minio https://helm.min.io/
73+
helm upgrade --wait -i minio minio/minio \
74+
--namespace minio \
75+
--set accessKey=myaccesskey \
76+
--set secretKey=mysecretkey \
77+
--set resources.requests.memory=128Mi \
78+
--set persistence.enable=false
79+
kubectl -n minio port-forward svc/minio 9000:9000 &>/dev/null &
80+
sleep 2
81+
wget -q https://dl.min.io/client/mc/release/linux-amd64/mc
82+
chmod +x mc
83+
./mc alias set minio http://localhost:9000 myaccesskey mysecretkey --api S3v4
84+
./mc mb minio/podinfo
85+
./mc cp --recursive ./config/testdata/minio/manifests minio/podinfo
86+
- name: Run S3 tests
87+
run: |
88+
kubectl -n source-system apply -f ./config/testdata/minio/source.yaml
89+
kubectl -n source-system wait bucket/podinfo --for=condition=ready --timeout=1m
6790
- name: Debug failure
6891
if: failure()
6992
run: |
@@ -72,3 +95,4 @@ jobs:
7295
kubectl -n source-system get helmcharts -oyaml
7396
kubectl -n source-system get all
7497
kubectl -n source-system logs deploy/source-controller
98+
kubectl -n minio get all

PROJECT

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ resources:
1010
- group: source
1111
kind: HelmChart
1212
version: v1alpha1
13+
- group: source
14+
kind: Bucket
15+
version: v1alpha1
1316
version: "2"

api/v1alpha1/bucket_types.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
Copyright 2020 The Flux CD contributors.
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 v1alpha1
18+
19+
import (
20+
"time"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
)
25+
26+
const (
27+
BucketKind = "Bucket"
28+
BucketTimeout = time.Second * 20
29+
)
30+
31+
// BucketSpec defines the desired state of an S3 compatible bucket
32+
type BucketSpec struct {
33+
// The S3 compatible storage provider name, default ('generic').
34+
// +kubebuilder:validation:Enum=generic;aws
35+
// +optional
36+
Provider string `json:"provider,omitempty"`
37+
38+
// The bucket name.
39+
// +required
40+
BucketName string `json:"bucketName"`
41+
42+
// The bucket endpoint address.
43+
// +required
44+
Endpoint string `json:"endpoint"`
45+
46+
// Insecure allows connecting to a non-TLS S3 HTTP endpoint.
47+
// +optional
48+
Insecure bool `json:"insecure,omitempty"`
49+
50+
// The bucket region.
51+
// +optional
52+
Region string `json:"region,omitempty"`
53+
54+
// The secret name containing the bucket accesskey and secretkey.
55+
// +optional
56+
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
57+
58+
// The interval at which to check for bucket updates.
59+
// +required
60+
Interval metav1.Duration `json:"interval"`
61+
62+
// The timeout for download operations, default ('20s').
63+
// +optional
64+
Timeout *metav1.Duration `json:"timeout,omitempty"`
65+
66+
// Ignore overrides the set of excluded patterns in the .sourceignore
67+
// format (which is the same as .gitignore).
68+
// +optional
69+
Ignore *string `json:"ignore,omitempty"`
70+
}
71+
72+
const (
73+
GenericBucketProvider string = "generic"
74+
AmazonBucketProvider string = "aws"
75+
)
76+
77+
// BucketStatus defines the observed state of a bucket
78+
type BucketStatus struct {
79+
// ObservedGeneration is the last observed generation.
80+
// +optional
81+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
82+
83+
// Conditions holds the conditions for the Bucket.
84+
// +optional
85+
Conditions []SourceCondition `json:"conditions,omitempty"`
86+
87+
// URL is the download link for the artifact output of the last Bucket sync.
88+
// +optional
89+
URL string `json:"url,omitempty"`
90+
91+
// Artifact represents the output of the last successful Bucket sync.
92+
// +optional
93+
Artifact *Artifact `json:"artifact,omitempty"`
94+
}
95+
96+
const (
97+
// BucketOperationSucceedReason represents the fact that the bucket listing
98+
// and download operations succeeded.
99+
BucketOperationSucceedReason string = "BucketOperationSucceed"
100+
101+
// BucketOperationFailedReason represents the fact that the bucket listing
102+
// or download operations failed.
103+
BucketOperationFailedReason string = "BucketOperationFailed"
104+
)
105+
106+
// BucketProgressing resets the conditions of the Bucket
107+
// to SourceCondition of type Ready with status unknown and
108+
// progressing reason and message. It returns the modified Bucket.
109+
func BucketProgressing(bucket Bucket) Bucket {
110+
bucket.Status.ObservedGeneration = bucket.Generation
111+
bucket.Status.URL = ""
112+
bucket.Status.Conditions = []SourceCondition{}
113+
SetBucketCondition(&bucket, ReadyCondition, corev1.ConditionUnknown, ProgressingReason, "reconciliation in progress")
114+
return bucket
115+
}
116+
117+
// SetBucketCondition sets the given condition with the given status, reason and message on the Bucket.
118+
func SetBucketCondition(bucket *Bucket, condition string, status corev1.ConditionStatus, reason, message string) {
119+
bucket.Status.Conditions = filterOutSourceCondition(bucket.Status.Conditions, condition)
120+
bucket.Status.Conditions = append(bucket.Status.Conditions, SourceCondition{
121+
Type: condition,
122+
Status: status,
123+
LastTransitionTime: metav1.Now(),
124+
Reason: reason,
125+
Message: message,
126+
})
127+
}
128+
129+
// BucketReady sets the given artifact and url on the Bucket
130+
// and sets the ReadyCondition to True, with the given reason and
131+
// message. It returns the modified Bucket.
132+
func BucketReady(repository Bucket, artifact Artifact, url, reason, message string) Bucket {
133+
repository.Status.Artifact = &artifact
134+
repository.Status.URL = url
135+
SetBucketCondition(&repository, ReadyCondition, corev1.ConditionTrue, reason, message)
136+
return repository
137+
}
138+
139+
// BucketNotReady sets the ReadyCondition on the given Bucket
140+
// to False, with the given reason and message. It returns the modified Bucket.
141+
func BucketNotReady(repository Bucket, reason, message string) Bucket {
142+
SetBucketCondition(&repository, ReadyCondition, corev1.ConditionFalse, reason, message)
143+
return repository
144+
}
145+
146+
// BucketReadyMessage returns the message of the SourceCondition
147+
// of type Ready with status true if present, or an empty string.
148+
func BucketReadyMessage(repository Bucket) string {
149+
for _, condition := range repository.Status.Conditions {
150+
if condition.Type == ReadyCondition && condition.Status == corev1.ConditionTrue {
151+
return condition.Message
152+
}
153+
}
154+
return ""
155+
}
156+
157+
// GetTimeout returns the configured timeout or the default.
158+
func (in *Bucket) GetTimeout() time.Duration {
159+
if in.Spec.Timeout != nil {
160+
return in.Spec.Timeout.Duration
161+
}
162+
return BucketTimeout
163+
}
164+
165+
// GetArtifact returns the latest artifact from the source
166+
// if present in the status sub-resource.
167+
func (in *Bucket) GetArtifact() *Artifact {
168+
return in.Status.Artifact
169+
}
170+
171+
// GetInterval returns the interval at which the source is updated.
172+
func (in *Bucket) GetInterval() metav1.Duration {
173+
return in.Spec.Interval
174+
}
175+
176+
// +genclient
177+
// +genclient:Namespaced
178+
// +kubebuilder:object:root=true
179+
// +kubebuilder:subresource:status
180+
// +kubebuilder:printcolumn:name="URL",type=string,JSONPath=`.spec.url`
181+
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
182+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
183+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
184+
185+
// Bucket is the Schema for the buckets API
186+
type Bucket struct {
187+
metav1.TypeMeta `json:",inline"`
188+
metav1.ObjectMeta `json:"metadata,omitempty"`
189+
190+
Spec BucketSpec `json:"spec,omitempty"`
191+
Status BucketStatus `json:"status,omitempty"`
192+
}
193+
194+
// +kubebuilder:object:root=true
195+
196+
// BucketList contains a list of Bucket
197+
type BucketList struct {
198+
metav1.TypeMeta `json:",inline"`
199+
metav1.ListMeta `json:"metadata,omitempty"`
200+
Items []Bucket `json:"items"`
201+
}
202+
203+
func init() {
204+
SchemeBuilder.Register(&Bucket{}, &BucketList{})
205+
}

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 117 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)