Skip to content

Commit ef45e62

Browse files
committed
Use ephemeral S3 buckets for E2E tests
Use S3 buckets created during the lifecycle of a test instead of a static one and provide the capability to make them read-only public. Signed-off-by: Arnaud Meukam <[email protected]>
1 parent 81b4201 commit ef45e62

File tree

2 files changed

+167
-1
lines changed

2 files changed

+167
-1
lines changed

tests/e2e/kubetest2-kops/aws/s3.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
Copyright 2024 The Kubernetes 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 aws
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"regexp"
24+
"strings"
25+
"time"
26+
27+
"github.com/aws/aws-sdk-go-v2/aws"
28+
awsconfig "github.com/aws/aws-sdk-go-v2/config"
29+
"github.com/aws/aws-sdk-go-v2/service/s3"
30+
"github.com/aws/aws-sdk-go-v2/service/s3/types"
31+
"github.com/aws/aws-sdk-go-v2/service/sts"
32+
"k8s.io/klog/v2"
33+
)
34+
35+
// It contains S3Client, an Amazon S3 service client that is used to perform bucket
36+
// and object actions.
37+
type awsClient struct {
38+
S3Client *s3.Client
39+
}
40+
41+
// AWSBucketName constructs an unique bucket name using the AWS account ID
42+
func AWSBucketName(ctx context.Context) (string, error) {
43+
config, err := awsconfig.LoadDefaultConfig(ctx)
44+
if err != nil {
45+
return "", fmt.Errorf("failed to load AWS config: %w", err)
46+
}
47+
48+
stsSvc := sts.NewFromConfig(config)
49+
callerIdentity, err := stsSvc.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
50+
if err != nil {
51+
return "", fmt.Errorf("building AWS STS presigned request: %w", err)
52+
}
53+
54+
// Add timestamp suffix
55+
timestamp := time.Now().Format("01022006")
56+
bucket := fmt.Sprintf("k8s-infra-kops-%s", *callerIdentity.Account)
57+
bucket = fmt.Sprintf("%s-%s", bucket, timestamp)
58+
59+
bucket = strings.ToLower(bucket)
60+
bucket = regexp.MustCompile("[^a-z0-9-]").ReplaceAllString(bucket, "") // Only allow lowercase letters, numbers, and hyphens
61+
62+
if len(bucket) > 63 {
63+
bucket = bucket[:63] // Max length is 63
64+
}
65+
66+
return bucket, nil
67+
}
68+
69+
func (client awsClient) EnsureS3Bucket(ctx context.Context, bucketName string, publicRead bool) error {
70+
_, err := client.S3Client.CreateBucket(ctx, &s3.CreateBucketInput{
71+
Bucket: aws.String(bucketName),
72+
CreateBucketConfiguration: &types.CreateBucketConfiguration{
73+
LocationConstraint: types.BucketLocationConstraintUsEast2,
74+
},
75+
})
76+
77+
var exists *types.BucketAlreadyExists
78+
if err != nil {
79+
if errors.As(err, &exists) {
80+
klog.Infof("Bucket %s already exists.\n", bucketName)
81+
err = exists
82+
}
83+
} else {
84+
err := s3.NewBucketExistsWaiter(client.S3Client).Wait(
85+
ctx, &s3.HeadBucketInput{
86+
Bucket: aws.String(bucketName),
87+
},
88+
time.Minute)
89+
if err != nil {
90+
klog.Infof("Failed attempt to wait for bucket %s to exist.", bucketName)
91+
}
92+
}
93+
94+
klog.Infof("Bucket %s created successfully", bucketName)
95+
96+
if err == nil && publicRead {
97+
err = client.setPublicReadPolicy(ctx, bucketName)
98+
if err != nil {
99+
klog.Errorf("Failed to set public read policy on bucket %s: %v", bucketName, err)
100+
return err
101+
}
102+
klog.Infof("Public read policy set on bucket %s", bucketName)
103+
}
104+
105+
return err
106+
}
107+
108+
func (client awsClient) DeleteS3Bucket(ctx context.Context, bucketName string) error {
109+
_, err := client.S3Client.DeleteBucket(ctx, &s3.DeleteBucketInput{
110+
Bucket: aws.String(bucketName),
111+
})
112+
if err != nil {
113+
var noBucket *types.NoSuchBucket
114+
if errors.As(err, &noBucket) {
115+
klog.Infof("Bucket %s does not exits", bucketName)
116+
err = noBucket
117+
} else {
118+
klog.Infof("Couldn't delete bucket %s. Reason: %v", bucketName, err)
119+
}
120+
} else {
121+
err = s3.NewBucketNotExistsWaiter(client.S3Client).Wait(
122+
ctx, &s3.HeadBucketInput{
123+
Bucket: aws.String(bucketName),
124+
},
125+
time.Minute)
126+
if err != nil {
127+
klog.Infof("Failed attempt to wait for bucket %s to be deleted", bucketName)
128+
} else {
129+
klog.Infof("Bucket %s deleted", bucketName)
130+
}
131+
}
132+
return err
133+
}
134+
135+
func (client awsClient) setPublicReadPolicy(ctx context.Context, bucketName string) error {
136+
policy := fmt.Sprintf(`{
137+
"Version": "2012-10-17",
138+
"Statement": [
139+
{
140+
"Sid": "PublicReadGetObject",
141+
"Effect": "Allow",
142+
"Principal": "*",
143+
"Action": "s3:GetObject",
144+
"Resource": "arn:aws:s3:::%s/*"
145+
}
146+
]
147+
}`, bucketName)
148+
149+
_, err := client.S3Client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
150+
Bucket: aws.String(bucketName),
151+
Policy: aws.String(policy),
152+
})
153+
if err != nil {
154+
return fmt.Errorf("failed to put bucket policy for %s: %w", bucketName, err)
155+
}
156+
157+
return err
158+
}

tests/e2e/kubetest2-kops/deployer/common.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ limitations under the License.
1717
package deployer
1818

1919
import (
20+
"context"
2021
"errors"
2122
"fmt"
2223
"os"
2324
"path/filepath"
2425
"strings"
2526

2627
"k8s.io/klog/v2"
28+
"k8s.io/kops/tests/e2e/kubetest2-kops/aws"
2729
"k8s.io/kops/tests/e2e/kubetest2-kops/gce"
2830
"k8s.io/kops/tests/e2e/pkg/target"
2931
"k8s.io/kops/tests/e2e/pkg/util"
@@ -320,7 +322,13 @@ func (d *deployer) stateStore() string {
320322
if ss == "" {
321323
switch d.CloudProvider {
322324
case "aws":
323-
ss = "s3://k8s-kops-prow"
325+
ctx := context.TODO()
326+
bucketName, err := aws.AWSBucketName(ctx)
327+
if err := aws.EnsureS3Bucket(ctx, bucketName); err != nil {
328+
return err
329+
}
330+
klog.Infof("Generated bucket name:", bucketName)
331+
ss = "s3://" + bucketName
324332
case "gce":
325333
d.createBucket = true
326334
ss = "gs://" + gce.GCSBucketName(d.GCPProject, "state")

0 commit comments

Comments
 (0)