Skip to content

Commit 8c235e3

Browse files
nivr4Niv RavivshaiMoria
authored
Feat: Add system tests for the operator
* add kubebuild init files, titfile and few command to makefile for running locally * add kind config yaml for local k8s kind cluster * modify the make file , deploy working * define s3 bucket type * add readme and script for local running * add input validition and RetryPeriod * edit titfile * add aws client functions * insert create and delete bucket functionality , modify the tiltfile * add gettter to aws client , and helm commands to localstack * support encrypt bucket * add resource map to manage k8s resource agianst aws resource * fix delete resource * fix run loclal script, add logs to delete flow * fix deletion of bucket in aws * support deletion of bucket policy and creation and deletion of iam role * support deletion of bucket policy and creation and deletion of iam role * add bucket name validation * bucketname validation * syntax * refactor: remove unused files in bin folder + update git ignore * fix: change runLocalenv.sh script variable defenitions * add tests folder * update git ignore * add varieble file to config * remove region from s3 resource * move cred var to deploy yaml * fix: makefile controller-gen * add devmode var * add to readme: golang version * add cleanup bucket function to delete flow * move config varibels under controllers folder * edit logs * test * change delete logic * edit logs * chang looger logic, fix put tgs function * add logs for update flow * remove comment * a * change kind cluster * add system test , edit run local script to deploy ingress controller * change port to 4566, fix putting tags * edit: readme, add update test * crate k8s client function * crate k8s client function * fix scripts * edit delete bucket test * add logs to tests, fix update tags function and edit logs * change tag prefix var Co-authored-by: Niv Raviv <[email protected]> Co-authored-by: shaimoria <[email protected]>
1 parent 5d199fe commit 8c235e3

File tree

12 files changed

+350
-65
lines changed

12 files changed

+350
-65
lines changed

.dockerignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
22
# Ignore build and test binaries.
3-
bin/
3+
# bin/
44
testbin/

Makefile

+8-2
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,17 @@ delete-local-cluster:
167167
kind-load-controller:
168168
kind load docker-image $(IMG) --name $(CLUSTER_NAME)
169169

170-
.PHONY:run-local-aws
170+
.PHONY: run-local-aws
171171
run-local-aws:
172172
docker run --rm -it -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack
173173

174-
.PHONY:run-local-aws-on-cluster
174+
.PHONY: run-local-aws-on-cluster
175175
run-local-aws-on-cluster:
176176
helm repo add localstack-repo https://helm.localstack.cloud
177177
helm upgrade --install localstack localstack-repo/localstack -n $(NAMESPACE)
178+
echo "applying ingress controller kong"
179+
kubectl apply -f ./hack/ingress.yaml -n $(NAMESPACE)
180+
181+
.PHONY: deploy-ingress-controller
182+
deploy-ingress-controller:
183+
$(shell ./hack/scripts/deploy-ingress.sh)

README.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ Requirements:
1515

1616
**Quick Start**
1717

18-
This script will create kind cluster, build image and deploy it and will run local aws on cluster ([localstack](https://github.com/localstack/localstack))
18+
This script will create kind cluster,
19+
build image of the controller and deploy it,
20+
deploy kong ingress controller
21+
and will run local aws on cluster with ingress ([localstack](https://github.com/localstack/localstack))
1922
```bash
2023
sh ./hack/scripts/runLocalEnv.sh # you might need to run this as sudo if a regular user can't use docker
2124

@@ -25,7 +28,17 @@ sh ./hack/scripts/runLocalEnv.sh # you might need to run this as sudo if a regul
2528

2629
Todo
2730

28-
### Development using Tilt
31+
### **Run system tests**
32+
The tests run against your local kind cluster and the [localstack](https://github.com/localstack/localstack) service that run on your cluster.
33+
34+
run tests:
35+
```bash
36+
1. upload local env:
37+
1.1. sh ./hack/scripts/runLocalEnv.sh
38+
2. go test ./tests/systemTest/system_test.go -v # -v flag for log all tests as they are run
39+
```
40+
41+
### **Development using Tilt**
2942

3043
The recommended development flow is based on [Tilt](https://tilt.dev/) - it is used for quick iteration on code running in live containers.
3144
Setup based on [official docs](https://docs.tilt.dev/example_go.html) can be found in the Tiltfile.

Tiltfile

+41-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,50 @@
11
IMG = 'controller:tilt'
2-
docker_build(IMG, '.')
2+
load("ext://restart_process", "docker_build_with_restart")
33

4-
def yaml():
5-
return local('cd config/manager; kustomize edit set image controller=' + IMG + '; cd ../..; kustomize build config/default')
4+
DOCKERFILE = """FROM golang:alpine
5+
WORKDIR /
6+
COPY ./bin/manager /
7+
CMD ["/manager"]
8+
"""
69

10+
711
def manifests():
8-
return 'bin/controller-gen crd:trivialVersions=true rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases;'
12+
return './bin/controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases;'
13+
914

1015
def generate():
11-
return 'bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";'
16+
return './bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";'
1217

13-
def vetfmt():
14-
return 'go vet ./...; go fmt ./...'
1518

1619
def binary():
17-
return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/manager main.go'
18-
19-
local_resource('crd', manifests() + 'kustomize build config/crd | kubectl apply -f -', deps=["api"])
20-
21-
k8s_yaml(yaml())
22-
23-
local_resource('recompile', generate() + binary(), deps=['controllers', 'main.go'])
20+
return "CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/manager main.go"
21+
22+
# Deploy CRD
23+
local_resource(
24+
"CRD",
25+
manifests() + "kustomize build config/crd | kubectl apply -f -",
26+
deps=["api"],
27+
ignore=["*/*/zz_generated.deepcopy.go"],
28+
)
29+
30+
# Deploy manager
31+
watch_file("./config/")
32+
k8s_yaml(local('kustomize build config/default'))
33+
34+
local_resource(
35+
"Watch & Compile",
36+
generate() + binary(),
37+
deps=["controllers", "api", "main.go"],
38+
ignore=["*/*/zz_generated.deepcopy.go"],
39+
)
40+
41+
docker_build_with_restart(
42+
IMG,
43+
".",
44+
dockerfile_contents=DOCKERFILE,
45+
entrypoint=["/manager"],
46+
only=["./bin/manager"],
47+
live_update=[
48+
sync("./bin/manager", "/manager"),
49+
],
50+
)

controllers/aws/awsClient.go

+61-45
Original file line numberDiff line numberDiff line change
@@ -112,50 +112,70 @@ func (a *AwsClient) HandleBucketUpdate(bucketName string, bucketSpec *s3operator
112112
}
113113

114114
func (a *AwsClient) UpdateBucketTags(bucketName string, tagsToUpdate map[string]string) (bool, error) {
115-
a.Log.Info("UpdateBucketTags function")
116-
taggingOut, err := a.s3Client.GetBucketTagging(&s3.GetBucketTaggingInput{Bucket: aws.String(bucketName)})
115+
a.Log.V(1).Info("UpdateBucketTags function")
116+
if tagsToUpdate == nil {
117+
tagsToUpdate = map[string]string{}
118+
}
119+
tagsFromAws, err := a.s3Client.GetBucketTagging(&s3.GetBucketTaggingInput{Bucket: aws.String(bucketName)})
117120
if err != nil {
118121
a.Log.Error(err, "error from GetBucketTagging")
119122
return false, err
120123
}
121-
diffTags := a.FindDiffTags(tagsToUpdate, taggingOut.TagSet)
122-
if len(diffTags) > 0 {
124+
isDiffTags, diffTags := a.FindIfDiffTags(tagsToUpdate, tagsFromAws.TagSet)
125+
if isDiffTags {
123126
_, err := a.s3Client.PutBucketTagging(&s3.PutBucketTaggingInput{Bucket: &bucketName, Tagging: &s3.Tagging{TagSet: diffTags}})
124127
if err != nil {
125128
a.Log.Error(err, "error from PutBucketTagging")
126129
} else {
127130
a.Log.Info("finish to update tags")
128131

129132
}
133+
} else {
134+
a.Log.Info("no tags to update")
130135
}
131136
return true, nil
132-
133137
}
134-
func (a *AwsClient) FindDiffTags(tagsToUpdate map[string]string, tagsFromAws []*s3.Tag) []*s3.Tag {
135-
a.Log.Info("FindDiffTags function")
136-
var diffTags []*s3.Tag
137-
mapTagsFromAws := make(map[string]struct{}, len(tagsFromAws))
138+
func (a *AwsClient) FindIfDiffTags(tagsToUpdate map[string]string, tagsFromAws []*s3.Tag) (bool, []*s3.Tag) {
139+
a.Log.V(1).Info("FindDiffTags function")
140+
isDiffTags := false
141+
newTags := []*s3.Tag{}
142+
mapAwsTags := map[string]string{}
143+
138144
for _, tag := range tagsFromAws {
139-
mapTagsFromAws[tag.String()] = struct{}{}
145+
tagToCheck := *tag
146+
mapAwsTags[*tag.Key] = *tag.Value
147+
if len(*tagToCheck.Key) < len(config.TagPrefix()) || (*tagToCheck.Key)[:len(config.TagPrefix())] != config.TagPrefix() {
148+
newTags = append(newTags, &tagToCheck) //add all tags that dont have the operator prefix
149+
a.Log.Info("add tag from aws", "tag", tagToCheck)
150+
151+
} else { //all the tags from aws that have the Tag prefix
152+
tagKeyWithoutPrefix := (*tagToCheck.Key)[:len(config.TagPrefix())]
153+
val, ok := tagsToUpdate[tagKeyWithoutPrefix]
154+
if !ok || val != *tagToCheck.Value {
155+
isDiffTags = true
156+
a.Log.Info("found tags to update", "tagsToUpdate", tagToCheck)
157+
}
158+
}
140159
}
141-
for key, val := range tagsToUpdate {
142-
Tagkey := key
160+
for key, val := range tagsToUpdate { //add all the tags from resource to the tags array
161+
Tagkey := config.TagPrefix() + key
143162
Tagval := val
144163
tag := s3.Tag{Key: &Tagkey, Value: &Tagval}
145-
if _, found := mapTagsFromAws[tag.String()]; !found {
146-
diffTags = append(diffTags, &tag)
164+
a.Log.V(1).Info("add tag from spec", "tag", tag)
165+
newTags = append(newTags, &tag)
166+
val, ok := mapAwsTags[Tagkey]
167+
if !ok || val != Tagval {
168+
isDiffTags = true
169+
a.Log.Info("found tags to update", "tagsToUpdate", tag)
147170
}
171+
148172
}
149-
if len(diffTags) > 0 {
150-
a.Log.Info("found tags to update", "tagsToUpdate", diffTags)
151-
} else {
152-
a.Log.Info("no tags to update")
153-
}
154-
return diffTags
173+
a.Log.V(1).Info("returend from find diff Tags", "isDiffTags", isDiffTags, "newTags", newTags)
174+
return isDiffTags, newTags
155175
}
156176

157177
func (a *AwsClient) CreateBucket(bucketInput s3.CreateBucketInput) (*s3.CreateBucketOutput, error) {
158-
a.Log.Info("Starting to create S3 bucket on AWS", "bucket_name", *bucketInput.Bucket, "region", *bucketInput.CreateBucketConfiguration.LocationConstraint)
178+
a.Log.Info("Starting to create S3 bucket on AWS", "region", *bucketInput.CreateBucketConfiguration.LocationConstraint)
159179
res, err := a.s3Client.CreateBucket(&bucketInput)
160180
if err != nil { // cast err to awserr.Error to get the Code and
161181
if aerr, ok := err.(awserr.Error); ok {
@@ -172,19 +192,19 @@ func (a *AwsClient) CreateBucket(bucketInput s3.CreateBucketInput) (*s3.CreateBu
172192
}
173193
} else {
174194
// Message from an error.
175-
a.Log.Error(err, "error in creatBucket function", "bucket_name", *bucketInput.Bucket, "region", *bucketInput.CreateBucketConfiguration.LocationConstraint)
195+
a.Log.Error(err, "error in creatBucket function", "region", *bucketInput.CreateBucketConfiguration.LocationConstraint)
176196
return nil, err
177197
}
178198
}
179-
a.Log.Info("S3 bucket creation finished successfully", "bucket_name", *bucketInput.Bucket, "region", *bucketInput.CreateBucketConfiguration.LocationConstraint)
199+
a.Log.Info("S3 bucket creation finished successfully", "region", *bucketInput.CreateBucketConfiguration.LocationConstraint)
180200
return res, nil
181201

182202
}
183203

184204
func (a *AwsClient) PutBucketTagging(bucketName string, bucketTags *map[string]string) (bool, error) {
185205
tags := make([]*s3.Tag, 0)
186206
for key, val := range *bucketTags {
187-
Tagkey := key
207+
Tagkey := config.TagPrefix() + key
188208
Tagval := val
189209
tag := s3.Tag{
190210
Key: &Tagkey,
@@ -198,13 +218,13 @@ func (a *AwsClient) PutBucketTagging(bucketName string, bucketTags *map[string]s
198218
Bucket: aws.String(bucketName),
199219
Tagging: &s3.Tagging{TagSet: tags},
200220
}
201-
a.Log.Info("Adding Tags to s3 bucket", "bucket_name", *input.Bucket, "bucket_tags", *input.Tagging)
221+
a.Log.Info("Adding Tags to s3 bucket", "bucket_tags", *input.Tagging)
202222
_, err := a.s3Client.PutBucketTagging(input)
203223
if err != nil {
204-
a.Log.Error(err, "error PutBucketTagging", "bucket_name", *input.Bucket)
224+
a.Log.Error(err, "error PutBucketTagging")
205225
return false, err
206226
}
207-
a.Log.Info("S3 bucket tagging finished successfully", "bucket_name", *input.Bucket, "bucket_tags", *input.Tagging)
227+
a.Log.Info("S3 bucket tagging finished successfully", "bucket_tags", *input.Tagging)
208228
return true, nil
209229
}
210230

@@ -217,26 +237,26 @@ func (a *AwsClient) CreateBucketInput(bucketName string, bucketRegion string) *s
217237
}
218238

219239
func (a *AwsClient) DeleteBucket(bucketName string) (*s3.DeleteBucketOutput, error) {
220-
a.Log.Info("DeleteBucket function", "bucket_name", bucketName)
240+
a.Log.Info("DeleteBucket function")
221241
res, err := a.s3Client.DeleteBucket(&s3.DeleteBucketInput{Bucket: aws.String(bucketName)})
222242
return res, err
223243
}
224244
func (a *AwsClient) CleanupsBucket(bucketName string) error {
225-
a.Log.Info("CleanupsBucket function", "bucket_name", bucketName)
245+
a.Log.Info("CleanupsBucket function")
226246

227247
iter := s3manager.NewDeleteListIterator(a.s3Client, &s3.ListObjectsInput{
228248
Bucket: aws.String(bucketName),
229249
})
230250
if err := s3manager.NewBatchDeleteWithClient(a.s3Client).Delete(aws.BackgroundContext(), iter); err != nil {
231-
a.Log.Error(err, "Unable to delete objects from bucket", "bucket_name", bucketName)
251+
a.Log.Error(err, "Unable to delete objects from bucket")
232252
return err
233253
}
234-
a.Log.Info("succeded to cleanup bucket", "bucket_name", bucketName)
254+
a.Log.Info("succeded to cleanup bucket")
235255
return nil
236256
}
237257

238258
func (a *AwsClient) PutBucketPolicy(bucketName string, roleName string) (*s3.PutBucketPolicyOutput, error) {
239-
a.Log.Info("adding bucket policy for s3 bucket", "bucket_name", bucketName, "roleName:", roleName)
259+
a.Log.Info("adding bucket policy for s3 bucket", "roleName:", roleName)
240260

241261
// Create a policy using map interface. Filling in the bucket as the
242262
// resource.
@@ -261,7 +281,7 @@ func (a *AwsClient) PutBucketPolicy(bucketName string, roleName string) (*s3.Put
261281
}
262282
bucketPolicy, err := json.Marshal(AllPremisionToRole)
263283
if err != nil {
264-
a.Log.Error(err, "error in PutBucketPolicy in Marshal", "bucket_name", bucketName, "roleName:", roleName)
284+
a.Log.Error(err, "error in PutBucketPolicy in Marshal", "roleName:", roleName)
265285
return nil, err
266286

267287
}
@@ -274,13 +294,13 @@ func (a *AwsClient) PutBucketPolicy(bucketName string, roleName string) (*s3.Put
274294
if err != nil {
275295
a.Log.Error(err, "error in put bucket policy", bucketName, "policy:", *input.Policy)
276296
} else {
277-
a.Log.Info("Attach bucket policy to s3 bucket finished successfully", "bucket_name", bucketName, "policy:", *input.Policy)
297+
a.Log.Info("Attach bucket policy to s3 bucket finished successfully", "policy:", *input.Policy)
278298
}
279299
return res, err
280300
}
281301

282302
func (a *AwsClient) PutBucketEncrypt(bucketName string) (bool, error) {
283-
a.Log.Info("PutBucketEncrypt function", "bucket_name", bucketName)
303+
a.Log.Info("PutBucketEncrypt function")
284304
encryptRules := []*s3.ServerSideEncryptionRule{{
285305
BucketKeyEnabled: aws.Bool(true),
286306
ApplyServerSideEncryptionByDefault: &s3.ServerSideEncryptionByDefault{SSEAlgorithm: aws.String("AES256")}},
@@ -293,25 +313,25 @@ func (a *AwsClient) PutBucketEncrypt(bucketName string) (bool, error) {
293313
Bucket: &bucketName,
294314
ServerSideEncryptionConfiguration: &sSEncryptConfiguration,
295315
}
296-
a.Log.Info("PutBucketEncryption input", "bucket_name", bucketName, "ServerSideEncryptionConfiguration:", *input.ServerSideEncryptionConfiguration)
316+
a.Log.Info("PutBucketEncryption input", "ServerSideEncryptionConfiguration:", *input.ServerSideEncryptionConfiguration)
297317
_, err := a.s3Client.PutBucketEncryption(input)
298318
if err != nil {
299319
a.Log.Error(err, "not succsede to PutBucketEncrypt")
300320
return false, err
301321
}
302-
a.Log.Info("succeded to encrypt bucket", "bucket_name", bucketName)
322+
a.Log.Info("succeded to encrypt bucket")
303323
return true, nil
304324

305325
}
306326

307327
func (a *AwsClient) DeleteBucketPolicy(bucketName string) (*s3.DeleteBucketPolicyOutput, error) {
308-
a.Log.Info("DeleteBucketPolicy function", "bucket_name", bucketName)
328+
a.Log.Info("DeleteBucketPolicy function")
309329
input := &s3.DeleteBucketPolicyInput{
310330
Bucket: &bucketName,
311331
}
312332
res, err := a.s3Client.DeleteBucketPolicy(input)
313333
if err != nil {
314-
a.Log.Error(err, "error in DeleteBucketPolicy", "bucket_name", bucketName)
334+
a.Log.Error(err, "error in DeleteBucketPolicy")
315335
}
316336
return res, err
317337
}
@@ -340,10 +360,6 @@ func (a *AwsClient) getAllBucketsByTag(filterTag *s3.Tag) ([]*string, error) {
340360
}
341361
return buckets, nil
342362
}
343-
func (a *AwsClient) LogWithVal(bucketName string) *logr.Logger {
344-
logger := a.Log.WithValues("bucket_name", bucketName)
345-
return &logger
346-
}
347363

348364
func getRoleName(bucketName string) string {
349365
roleName := bucketName + "IAM-ROLE-S3Operator"
@@ -365,7 +381,7 @@ func CreateSession(Log *logr.Logger) *session.Session {
365381
return ses
366382
}
367383

368-
func setS3Client(Log *logr.Logger, ses *session.Session) *s3.S3 {
384+
func SetS3Client(Log *logr.Logger, ses *session.Session) *s3.S3 {
369385
Log.Info("create s3Client wit session", "session", *ses)
370386
s3Client := s3.New(ses)
371387
if s3Client == nil {
@@ -396,7 +412,7 @@ func setClients(Log *logr.Logger) (*s3.S3, *resourcegroupstaggingapi.ResourceGro
396412
Log.Error(err, "error in create new session")
397413
} else {
398414
Log.Info("session", "ses", ses)
399-
return setS3Client(Log, ses), setRGTAClient(Log, ses), setIamClient(Log, ses)
415+
return SetS3Client(Log, ses), setRGTAClient(Log, ses), setIamClient(Log, ses)
400416
}
401417
return &s3.S3{}, &resourcegroupstaggingapi.ResourceGroupsTaggingAPI{}, &iam.IAM{}
402418
}

controllers/config/variables.go

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var resourcePerPage int64 = 100
1818
var awsCredentialsChainVerboseErrors bool
1919
var awsS3ForcePathStyle bool
2020
var devMode bool
21+
var TAG_PREFIX = "s3.operator/"
2122

2223
func init() {
2324
var err error
@@ -92,3 +93,6 @@ func AwsS3ForcePathStyle() bool {
9293
func DevMode() bool {
9394
return devMode
9495
}
96+
func TagPrefix() string {
97+
return TAG_PREFIX
98+
}

controllers/s3bucket_controller.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type S3BucketReconciler struct {
5151
// For more details, check Reconcile and its Result here:
5252
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
5353
func (r *S3BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
54-
log := r.Log.WithValues("namespace", req.Namespace, "resource_name", req.Name)
54+
log := r.Log.WithValues("namespace", req.Namespace, "bucket_name", req.Name)
5555
var s3Bucket s3operatorv1.S3Bucket
5656

5757
errToGet := r.Get(context.TODO(), req.NamespacedName, &s3Bucket)

0 commit comments

Comments
 (0)