Skip to content

Commit 815bfa5

Browse files
committed
basic eks cluster creation
1 parent fbf0a53 commit 815bfa5

6 files changed

Lines changed: 262 additions & 45 deletions

File tree

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ require (
1010
github.com/aws/aws-sdk-go-v2/config v1.31.8
1111
github.com/aws/aws-sdk-go-v2/credentials v1.18.12
1212
github.com/aws/aws-sdk-go-v2/service/ec2 v1.251.2
13+
github.com/aws/aws-sdk-go-v2/service/eks v1.73.3
14+
github.com/aws/aws-sdk-go-v2/service/iam v1.47.5
1315
github.com/bojanz/currency v1.3.1
1416
github.com/cenkalti/backoff v2.2.1+incompatible
1517
github.com/cenkalti/backoff/v4 v4.3.0
@@ -20,6 +22,7 @@ require (
2022
github.com/nebius/gosdk v0.0.0-20250826102719-940ad1dfb5de
2123
github.com/stretchr/testify v1.11.0
2224
golang.org/x/crypto v0.41.0
25+
google.golang.org/grpc v1.75.0
2326
)
2427

2528
require (
@@ -48,7 +51,6 @@ require (
4851
golang.org/x/sys v0.35.0 // indirect
4952
golang.org/x/text v0.28.0 // indirect
5053
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
51-
google.golang.org/grpc v1.75.0 // indirect
5254
google.golang.org/protobuf v1.36.8 // indirect
5355
gopkg.in/yaml.v3 v3.0.1 // indirect
5456
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d
2020
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
2121
github.com/aws/aws-sdk-go-v2/service/ec2 v1.251.2 h1:6TssXFfLHcwUS5E3MdYKkCFeOrYVBlDhJjs5kRJp0ic=
2222
github.com/aws/aws-sdk-go-v2/service/ec2 v1.251.2/go.mod h1:MXJiLJZtMqb2dVXgEIn35d5+7MqLd4r8noLen881kpk=
23+
github.com/aws/aws-sdk-go-v2/service/eks v1.73.3 h1:V6MAr82kSLdj3/tN4UcPtlXDbvkNcAxsIvq59CNe704=
24+
github.com/aws/aws-sdk-go-v2/service/eks v1.73.3/go.mod h1:FeDTTHze8jWVCZBiMkUYxJ/TQdOpTf9zbJjf0RI0ajo=
25+
github.com/aws/aws-sdk-go-v2/service/iam v1.47.5 h1:o2gRl9x3A/Sp6q4oHinnrS+2AC9Ud8DaG4JL9ygMACk=
26+
github.com/aws/aws-sdk-go-v2/service/iam v1.47.5/go.mod h1:0y7wFmnEg9xTZxjmr2gHQ4xOHpCfrt70lFWTOAkrij4=
2327
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
2428
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
2529
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8=

v1/kubernetes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const (
6262

6363
type CreateClusterArgs struct {
6464
Name string
65+
RefID string
6566
VPCID string
6667
SubnetIDs []string
6768
KubernetesVersion string

v1/providers/aws/kubernetes.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package v1
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"time"
8+
9+
"github.com/aws/aws-sdk-go-v2/aws"
10+
"github.com/aws/aws-sdk-go-v2/service/eks"
11+
ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
12+
"github.com/aws/aws-sdk-go-v2/service/iam"
13+
iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
14+
15+
v1 "github.com/brevdev/cloud/v1"
16+
)
17+
18+
var _ v1.CloudMaintainKubernetes = &AWSClient{}
19+
20+
func (c *AWSClient) CreateCluster(ctx context.Context, args v1.CreateClusterArgs) (*v1.Cluster, error) {
21+
// Create the AWS client in the specified region
22+
awsClient := awsClient{
23+
eksClient: eks.NewFromConfig(c.awsConfig, func(o *eks.Options) {
24+
o.Region = args.Location
25+
}),
26+
iamClient: iam.NewFromConfig(c.awsConfig, func(o *iam.Options) {
27+
o.Region = args.Location
28+
}),
29+
}
30+
31+
// Create the cluster
32+
awsCluster, err := createEKSCluster(ctx, awsClient, args)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
return &v1.Cluster{
38+
RefID: args.RefID,
39+
Name: *awsCluster.Name,
40+
Status: v1.ClusterStatusAvailable,
41+
// NodeGroups: args.NodeGroups,
42+
}, nil
43+
}
44+
45+
func createEKSCluster(ctx context.Context, awsClient awsClient, args v1.CreateClusterArgs) (*ekstypes.Cluster, error) {
46+
serviceRoleARN, err := getOrCreateServiceRoleARN(ctx, awsClient, args)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
eksCluster, err := createCluster(ctx, awsClient, args, serviceRoleARN)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
err = installEKSAddons(ctx, awsClient, eksCluster)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
return eksCluster, nil
62+
}
63+
64+
func getOrCreateServiceRoleARN(ctx context.Context, awsClient awsClient, args v1.CreateClusterArgs) (string, error) {
65+
serviceRoleName := fmt.Sprintf("%s-service-role", args.RefID)
66+
67+
// Get and return the role if it exists
68+
serviceRole, err := awsClient.iamClient.GetRole(ctx, &iam.GetRoleInput{
69+
RoleName: aws.String(serviceRoleName),
70+
})
71+
if err == nil {
72+
return *serviceRole.Role.Arn, nil
73+
} else {
74+
// The error may be a NoSuchEntityException. If it is, we should ignore the error and create the role.
75+
var noSuchEntityError *iamtypes.NoSuchEntityException
76+
if !errors.As(err, &noSuchEntityError) {
77+
// The error is not a no such entity error, so we return the error
78+
return "", err
79+
}
80+
}
81+
82+
// Create the role
83+
input := &iam.CreateRoleInput{
84+
RoleName: aws.String(serviceRoleName),
85+
Description: aws.String("Role for EKS cluster"),
86+
AssumeRolePolicyDocument: aws.String(`{
87+
"Version": "2012-10-17",
88+
"Statement": [
89+
{
90+
"Effect": "Allow",
91+
"Principal": {
92+
"Service": "eks.amazonaws.com"
93+
},
94+
"Action": "sts:AssumeRole"
95+
}
96+
]
97+
}`),
98+
Tags: []iamtypes.Tag{
99+
{
100+
Key: aws.String(tagBrevRefID),
101+
Value: aws.String(args.RefID),
102+
},
103+
{
104+
Key: aws.String("CreatedBy"),
105+
Value: aws.String(tagBrevCloudSDK),
106+
},
107+
},
108+
}
109+
output, err := awsClient.iamClient.CreateRole(ctx, input)
110+
if err != nil {
111+
return "", err
112+
}
113+
114+
// Attach the AmazonEKSClusterPolicy to the role
115+
_, err = awsClient.iamClient.AttachRolePolicy(ctx, &iam.AttachRolePolicyInput{
116+
RoleName: aws.String(serviceRoleName),
117+
PolicyArn: aws.String("arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"),
118+
})
119+
if err != nil {
120+
return "", err
121+
}
122+
123+
return *output.Role.Arn, nil
124+
}
125+
126+
func createCluster(ctx context.Context, awsClient awsClient, args v1.CreateClusterArgs, serviceRoleARN string) (*ekstypes.Cluster, error) {
127+
tags := map[string]string{
128+
"Name": args.Name,
129+
tagBrevRefID: args.RefID,
130+
"CreatedBy": tagBrevCloudSDK,
131+
}
132+
133+
input := &eks.CreateClusterInput{
134+
Name: aws.String(args.Name),
135+
Version: aws.String(args.KubernetesVersion),
136+
RoleArn: aws.String(serviceRoleARN),
137+
ResourcesVpcConfig: &ekstypes.VpcConfigRequest{
138+
SubnetIds: args.SubnetIDs,
139+
},
140+
Tags: tags,
141+
}
142+
143+
output, err := awsClient.eksClient.CreateCluster(ctx, input)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
// Wait for the cluster to be active
149+
w := eks.NewClusterActiveWaiter(awsClient.eksClient, func(o *eks.ClusterActiveWaiterOptions) {
150+
o.MaxDelay = 30 * time.Second
151+
o.MinDelay = 10 * time.Second
152+
})
153+
err = w.Wait(ctx, &eks.DescribeClusterInput{
154+
Name: output.Cluster.Name,
155+
}, 10*time.Minute)
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
return output.Cluster, nil
161+
}
162+
163+
func installEKSAddons(ctx context.Context, awsClient awsClient, eksCluster *ekstypes.Cluster) error {
164+
err := installEKSAddon(ctx, awsClient, eksCluster, "vpc-cni")
165+
if err != nil {
166+
return err
167+
}
168+
169+
err = installEKSAddon(ctx, awsClient, eksCluster, "eks-pod-identity-agent")
170+
if err != nil {
171+
return err
172+
}
173+
174+
return nil
175+
}
176+
177+
func installEKSAddon(ctx context.Context, awsClient awsClient, eksCluster *ekstypes.Cluster, addonName string) error {
178+
_, err := awsClient.eksClient.CreateAddon(ctx, &eks.CreateAddonInput{
179+
ClusterName: eksCluster.Name,
180+
AddonName: aws.String(addonName),
181+
})
182+
if err != nil {
183+
return err
184+
}
185+
186+
// Wait for the addon to be created
187+
w := eks.NewAddonActiveWaiter(awsClient.eksClient, func(o *eks.AddonActiveWaiterOptions) {
188+
o.MaxDelay = 30 * time.Second
189+
o.MinDelay = 10 * time.Second
190+
})
191+
err = w.Wait(ctx, &eks.DescribeAddonInput{
192+
ClusterName: eksCluster.Name,
193+
AddonName: aws.String(addonName),
194+
}, 10*time.Minute)
195+
if err != nil {
196+
return err
197+
}
198+
199+
return nil
200+
}
201+
202+
func (c *AWSClient) GetCluster(ctx context.Context, args v1.GetClusterArgs) (*v1.Cluster, error) {
203+
return nil, nil
204+
}
205+
206+
func (c *AWSClient) DeleteCluster(ctx context.Context, args v1.DeleteClusterArgs) error {
207+
return nil
208+
}

0 commit comments

Comments
 (0)