Skip to content

Commit 255229d

Browse files
authored
Improve ephemeral S3 buckets implementation for tests
Signed-off-by: Marko Mudrinić <[email protected]>
1 parent d73a286 commit 255229d

File tree

5 files changed

+132
-82
lines changed

5 files changed

+132
-82
lines changed

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

Lines changed: 78 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -32,46 +32,44 @@ import (
3232
"k8s.io/klog/v2"
3333
)
3434

35-
// We need to pick some region to query the AWS APIs through, even if we are not running on AWS.
35+
// defaultRegion is the region to query the AWS APIs through, this can be any AWS region is required even if we are not
36+
// running on AWS.
3637
const defaultRegion = "us-east-2"
3738

38-
// It contains S3Client, an Amazon S3 service client that is used to perform bucket
39-
// and object actions.
40-
type awsClient struct {
41-
S3Client *s3.Client
39+
// Client contains S3 and STS clients that are used to perform bucket and object actions.
40+
type Client struct {
41+
s3Client *s3.Client
42+
stsClient *sts.Client
4243
}
4344

44-
func NewAWSClient(ctx context.Context) (*awsClient, error) {
45+
// NewAWSClient returns a new instance of awsClient configured to work in the default region (us-east-2).
46+
func NewClient(ctx context.Context) (*Client, error) {
4547
cfg, err := awsconfig.LoadDefaultConfig(ctx,
4648
awsconfig.WithRegion(defaultRegion))
4749
if err != nil {
48-
return nil, fmt.Errorf("failed to load AWS config: %w", err)
50+
return nil, fmt.Errorf("loading AWS config: %w", err)
4951
}
50-
return &awsClient{
51-
S3Client: s3.NewFromConfig(cfg),
52+
53+
return &Client{
54+
s3Client: s3.NewFromConfig(cfg),
55+
stsClient: sts.NewFromConfig(cfg),
5256
}, nil
5357
}
5458

55-
// AWSBucketName constructs an unique bucket name using the AWS account ID on region us-east-2
56-
func AWSBucketName(ctx context.Context) (string, error) {
57-
config, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(string(types.BucketLocationConstraintUsEast2)))
58-
if err != nil {
59-
return "", fmt.Errorf("failed to load AWS config: %w", err)
60-
}
61-
62-
stsSvc := sts.NewFromConfig(config)
63-
callerIdentity, err := stsSvc.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
59+
// BucketName constructs an unique bucket name using the AWS account ID in the default region (us-east-2).
60+
func (c Client) BucketName(ctx context.Context) (string, error) {
61+
callerIdentity, err := c.stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
6462
if err != nil {
6563
return "", fmt.Errorf("building AWS STS presigned request: %w", err)
6664
}
6765

68-
// Add timestamp suffix
69-
timestamp := time.Now().Format("01022006")
70-
bucket := fmt.Sprintf("k8s-infra-kops-%s", *callerIdentity.Account)
71-
bucket = fmt.Sprintf("%s-%s", bucket, timestamp)
66+
// Construct the bucket name based on the AWS account ID and the current timestamp
67+
timestamp := time.Now().Format("20060102150405")
68+
bucket := fmt.Sprintf("k8s-infra-kops-%s-%s", *callerIdentity.Account, timestamp)
7269

7370
bucket = strings.ToLower(bucket)
74-
bucket = regexp.MustCompile("[^a-z0-9-]").ReplaceAllString(bucket, "") // Only allow lowercase letters, numbers, and hyphens
71+
// Only allow lowercase letters, numbers, and hyphens
72+
bucket = regexp.MustCompile("[^a-z0-9-]").ReplaceAllString(bucket, "")
7573

7674
if len(bucket) > 63 {
7775
bucket = bucket[:63] // Max length is 63
@@ -80,73 +78,88 @@ func AWSBucketName(ctx context.Context) (string, error) {
8078
return bucket, nil
8179
}
8280

83-
func (client awsClient) EnsureS3Bucket(ctx context.Context, bucketName string, publicRead bool) error {
84-
_, err := client.S3Client.CreateBucket(ctx, &s3.CreateBucketInput{
81+
// EnsureS3Bucket creates a new S3 bucket with the given name and public read permissions.
82+
func (c Client) EnsureS3Bucket(ctx context.Context, bucketName string, publicRead bool) error {
83+
_, err := c.s3Client.CreateBucket(ctx, &s3.CreateBucketInput{
8584
Bucket: aws.String(bucketName),
8685
CreateBucketConfiguration: &types.CreateBucketConfiguration{
87-
LocationConstraint: types.BucketLocationConstraintUsEast2,
86+
LocationConstraint: defaultRegion,
8887
},
8988
})
90-
91-
var exists *types.BucketAlreadyExists
9289
if err != nil {
90+
var exists *types.BucketAlreadyExists
9391
if errors.As(err, &exists) {
94-
klog.Infof("Bucket %s already exists.\n", bucketName)
95-
err = exists
96-
}
97-
} else {
98-
err := s3.NewBucketExistsWaiter(client.S3Client).Wait(
99-
ctx, &s3.HeadBucketInput{
100-
Bucket: aws.String(bucketName),
101-
},
102-
time.Minute)
103-
if err != nil {
104-
klog.Infof("Failed attempt to wait for bucket %s to exist.", bucketName)
92+
klog.Infof("Bucket %s already exists\n", bucketName)
93+
} else {
94+
klog.Infof("Error creating bucket %s, err: %v\n", bucketName, err)
10595
}
96+
97+
return fmt.Errorf("creating bucket %s: %w", bucketName, err)
98+
}
99+
100+
// Wait for the bucket to be created
101+
err = s3.NewBucketExistsWaiter(c.s3Client).Wait(
102+
ctx, &s3.HeadBucketInput{
103+
Bucket: aws.String(bucketName),
104+
},
105+
time.Minute)
106+
if err != nil {
107+
klog.Infof("Failed attempt to wait for bucket %s to exist, err: %v", bucketName, err)
108+
109+
return fmt.Errorf("waiting for bucket %s to exist: %w", bucketName, err)
106110
}
107111

108112
klog.Infof("Bucket %s created successfully", bucketName)
109113

110-
if err == nil && publicRead {
111-
err = client.setPublicReadPolicy(ctx, bucketName)
114+
if publicRead {
115+
err = c.setPublicReadPolicy(ctx, bucketName)
112116
if err != nil {
113-
klog.Errorf("Failed to set public read policy on bucket %s: %v", bucketName, err)
114-
return err
117+
klog.Errorf("Failed to set public read policy on bucket %s, err: %v", bucketName, err)
118+
119+
return fmt.Errorf("setting public read policy for bucket %s: %w", bucketName, err)
115120
}
121+
116122
klog.Infof("Public read policy set on bucket %s", bucketName)
117123
}
118124

119-
return err
125+
return nil
120126
}
121127

122-
func (client awsClient) DeleteS3Bucket(ctx context.Context, bucketName string) error {
123-
_, err := client.S3Client.DeleteBucket(ctx, &s3.DeleteBucketInput{
128+
// DeleteS3Bucket deletes a S3 bucket with the given name.
129+
func (c Client) DeleteS3Bucket(ctx context.Context, bucketName string) error {
130+
_, err := c.s3Client.DeleteBucket(ctx, &s3.DeleteBucketInput{
124131
Bucket: aws.String(bucketName),
125132
})
126133
if err != nil {
127134
var noBucket *types.NoSuchBucket
128135
if errors.As(err, &noBucket) {
129-
klog.Infof("Bucket %s does not exits", bucketName)
130-
err = noBucket
131-
} else {
132-
klog.Infof("Couldn't delete bucket %s. Reason: %v", bucketName, err)
133-
}
134-
} else {
135-
err = s3.NewBucketNotExistsWaiter(client.S3Client).Wait(
136-
ctx, &s3.HeadBucketInput{
137-
Bucket: aws.String(bucketName),
138-
},
139-
time.Minute)
140-
if err != nil {
141-
klog.Infof("Failed attempt to wait for bucket %s to be deleted", bucketName)
136+
klog.Infof("Bucket %s does not exits.", bucketName)
137+
138+
return nil
142139
} else {
143-
klog.Infof("Bucket %s deleted", bucketName)
140+
klog.Infof("Couldn't delete bucket %s, err: %v", bucketName, err)
141+
142+
return fmt.Errorf("deleting bucket %s: %w", bucketName, err)
144143
}
145144
}
146-
return err
145+
146+
err = s3.NewBucketNotExistsWaiter(c.s3Client).Wait(
147+
ctx, &s3.HeadBucketInput{
148+
Bucket: aws.String(bucketName),
149+
},
150+
time.Minute)
151+
if err != nil {
152+
klog.Infof("Failed attempt to wait for bucket %s to be deleted, err: %v", bucketName, err)
153+
154+
return fmt.Errorf("waiting for bucket %s to be deleted, err: %w", bucketName, err)
155+
}
156+
157+
klog.Infof("Bucket %s deleted", bucketName)
158+
159+
return nil
147160
}
148161

149-
func (client awsClient) setPublicReadPolicy(ctx context.Context, bucketName string) error {
162+
func (c Client) setPublicReadPolicy(ctx context.Context, bucketName string) error {
150163
policy := fmt.Sprintf(`{
151164
"Version": "2012-10-17",
152165
"Statement": [
@@ -160,13 +173,13 @@ func (client awsClient) setPublicReadPolicy(ctx context.Context, bucketName stri
160173
]
161174
}`, bucketName)
162175

163-
_, err := client.S3Client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
176+
_, err := c.s3Client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
164177
Bucket: aws.String(bucketName),
165178
Policy: aws.String(policy),
166179
})
167180
if err != nil {
168-
return fmt.Errorf("failed to put bucket policy for %s: %w", bucketName, err)
181+
return fmt.Errorf("putting bucket policy for %s: %w", bucketName, err)
169182
}
170183

171-
return err
184+
return nil
172185
}

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ func (d *deployer) initialize() error {
5353

5454
switch d.CloudProvider {
5555
case "aws":
56+
client, err := aws.NewClient(context.Background())
57+
if err != nil {
58+
return fmt.Errorf("init failed to build AWS client: %w", err)
59+
}
60+
d.aws = client
61+
5662
if d.SSHPrivateKeyPath == "" {
5763
d.SSHPrivateKeyPath = os.Getenv("AWS_SSH_PRIVATE_KEY_FILE")
5864
}
@@ -318,23 +324,20 @@ func defaultClusterName(cloudProvider string) (string, error) {
318324
// stateStore returns the kops state store to use
319325
// defaulting to values used in prow jobs
320326
func (d *deployer) stateStore() string {
327+
if d.stateStoreName != "" {
328+
return d.stateStoreName
329+
}
321330
ss := os.Getenv("KOPS_STATE_STORE")
322331
if ss == "" {
323332
switch d.CloudProvider {
324333
case "aws":
325-
ctx := context.TODO()
326-
bucketName, err := aws.AWSBucketName(ctx)
334+
ctx := context.Background()
335+
bucketName, err := d.aws.BucketName(ctx)
327336
if err != nil {
328337
klog.Fatalf("Failed to generate bucket name: %v", err)
329-
}
330-
awsClient, err := aws.NewAWSClient(ctx)
331-
if err != nil {
332-
klog.Fatalf("failed to load client config: %v", err)
333-
}
334-
if err := awsClient.EnsureS3Bucket(ctx, bucketName, d.PublicReadOnlyBucket); err != nil {
335-
klog.Fatalf("Failed to ensure S3 bucket exists: %v", err)
336338
return ""
337339
}
340+
d.createBucket = true
338341
ss = "s3://" + bucketName
339342
case "gce":
340343
d.createBucket = true
@@ -343,22 +346,31 @@ func (d *deployer) stateStore() string {
343346
ss = "do://e2e-kops-space"
344347
}
345348
}
349+
350+
d.stateStoreName = ss
346351
return ss
347352
}
348353

349354
// discoveryStore returns the VFS path to use for public OIDC documents
350355
func (d *deployer) discoveryStore() string {
356+
if d.discoveryStoreName != "" {
357+
return d.discoveryStoreName
358+
}
351359
discovery := os.Getenv("KOPS_DISCOVERY_STORE")
352360
if discovery == "" {
353361
switch d.CloudProvider {
354362
case "aws":
355363
discovery = "s3://k8s-kops-ci-prow"
356364
}
357365
}
366+
d.discoveryStoreName = discovery
358367
return discovery
359368
}
360369

361370
func (d *deployer) stagingStore() string {
371+
if d.stagingStoreName != "" {
372+
return d.stagingStoreName
373+
}
362374
sb := os.Getenv("KOPS_STAGING_BUCKET")
363375
if sb == "" {
364376
switch d.CloudProvider {
@@ -367,6 +379,7 @@ func (d *deployer) stagingStore() string {
367379
sb = "gs://" + gce.GCSBucketName(d.GCPProject, "staging")
368380
}
369381
}
382+
d.stagingStoreName = sb
370383
return sb
371384
}
372385

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/octago/sflags/gen/gpflag"
2626
"github.com/spf13/pflag"
2727
"k8s.io/klog/v2"
28+
"k8s.io/kops/tests/e2e/kubetest2-kops/aws"
2829
"k8s.io/kops/tests/e2e/kubetest2-kops/builder"
2930
"k8s.io/kops/tests/e2e/pkg/target"
3031

@@ -57,8 +58,6 @@ type deployer struct {
5758
CreateArgs string `flag:"create-args" desc:"Extra space-separated arguments passed to 'kops create cluster'"`
5859
KopsBinaryPath string `flag:"kops-binary-path" desc:"The path to kops executable used for testing"`
5960
KubernetesFeatureGates string `flag:"kubernetes-feature-gates" desc:"Feature Gates to enable on Kubernetes components"`
60-
createBucket bool `flag:"-"`
61-
PublicReadOnlyBucket bool `flag:"-"`
6261

6362
// ControlPlaneCount specifies the number of VMs in the control-plane.
6463
ControlPlaneCount int `flag:"control-plane-count" desc:"Number of control-plane instances"`
@@ -91,6 +90,13 @@ type deployer struct {
9190
manifestPath string
9291
terraform *target.Terraform
9392

93+
aws *aws.Client
94+
95+
createBucket bool
96+
stateStoreName string
97+
discoveryStoreName string
98+
stagingStoreName string
99+
94100
// boskos struct field will be non-nil when the deployer is
95101
// using boskos to acquire a GCP project
96102
boskos *client.Client

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package deployer
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"strings"
2223

@@ -72,9 +73,17 @@ func (d *deployer) Down() error {
7273
return err
7374
}
7475

75-
if d.CloudProvider == "gce" && d.createBucket {
76-
gce.DeleteGCSBucket(d.stateStore(), d.GCPProject)
77-
gce.DeleteGCSBucket(d.stagingStore(), d.GCPProject)
76+
if d.createBucket {
77+
switch d.CloudProvider {
78+
case "aws":
79+
ctx := context.Background()
80+
if err := d.aws.DeleteS3Bucket(ctx, d.stateStore()); err != nil {
81+
return err
82+
}
83+
case "gce":
84+
gce.DeleteGCSBucket(d.stateStore(), d.GCPProject)
85+
gce.DeleteGCSBucket(d.stagingStore(), d.GCPProject)
86+
}
7887
}
7988

8089
if d.boskos != nil {

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package deployer
1818

1919
import (
20+
"context"
2021
"errors"
2122
"fmt"
2223
osexec "os/exec"
@@ -61,9 +62,17 @@ func (d *deployer) Up() error {
6162
_ = d.Down()
6263
}
6364

64-
if d.CloudProvider == "gce" && d.createBucket {
65-
if err := gce.EnsureGCSBucket(d.stateStore(), d.GCPProject, false); err != nil {
66-
return err
65+
if d.createBucket {
66+
switch d.CloudProvider {
67+
case "aws":
68+
ctx := context.Background()
69+
if err := d.aws.EnsureS3Bucket(ctx, d.stateStore(), false); err != nil {
70+
return err
71+
}
72+
case "gce":
73+
if err := gce.EnsureGCSBucket(d.stateStore(), d.GCPProject, false); err != nil {
74+
return err
75+
}
6776
}
6877
}
6978

0 commit comments

Comments
 (0)