Skip to content

Commit 869e502

Browse files
committed
begin nebius impl
1 parent ee5b7ed commit 869e502

9 files changed

Lines changed: 472 additions & 45 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
99
github.com/aws/aws-sdk-go-v2 v1.39.0
1010
github.com/aws/aws-sdk-go-v2/config v1.31.8
11+
github.com/aws/aws-sdk-go-v2/credentials v1.18.12
1112
github.com/aws/aws-sdk-go-v2/service/ec2 v1.251.2
1213
github.com/bojanz/currency v1.3.1
1314
github.com/cenkalti/backoff v2.2.1+incompatible
@@ -24,7 +25,6 @@ require (
2425
require (
2526
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.8-20250717185734-6c6e0d3c608e.1 // indirect
2627
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
27-
github.com/aws/aws-sdk-go-v2/credentials v1.18.12 // indirect
2828
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect
2929
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect
3030
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect

v1/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ type CloudClient interface {
4242
CloudModifyFirewall
4343
CloudInstanceTags
4444
UpdateHandler
45+
CloudMaintainVPC
4546
}

v1/notimplemented.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ func (c notImplCloudClient) CreateVPC(_ context.Context, _ CreateVPCArgs) (*VPC,
131131
return nil, ErrNotImplemented
132132
}
133133

134+
func (c notImplCloudClient) GetVPC(_ context.Context, _ GetVPCArgs) (*VPC, error) {
135+
return nil, ErrNotImplemented
136+
}
137+
134138
func (c notImplCloudClient) DeleteVPC(_ context.Context, _ DeleteVPCArgs) error {
135139
return ErrNotImplemented
136140
}

v1/providers/aws/network.go

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
tagBrevRefID = "brev-ref-id"
1717
tagBrevVPCID = "brev-vpc-id"
1818
tagBrevSubnetType = "brev-subnet-type"
19+
tagBrevCloudSDK = "brev-cloud-sdk"
1920

2021
natGatewayDeleteWaitInterval = 10 * time.Second
2122
natGatewayCreateWaitInterval = 10 * time.Second
@@ -71,6 +72,7 @@ func createVPC(ctx context.Context, awsClient *ec2.Client, name string, cidrBloc
7172
tags := makeTags(map[string]string{
7273
"Name": name,
7374
tagBrevRefID: brevRefID,
75+
"CreatedBy": tagBrevCloudSDK,
7476
})
7577

7678
input := &ec2.CreateVpcInput{
@@ -125,6 +127,7 @@ func createInternetGateway(ctx context.Context, awsClient *ec2.Client, vpc *type
125127
tags := makeTags(map[string]string{
126128
"Name": fmt.Sprintf("%s-public", *vpc.VpcId),
127129
tagBrevVPCID: *vpc.VpcId,
130+
"CreatedBy": tagBrevCloudSDK,
128131
})
129132

130133
createInput := &ec2.CreateInternetGatewayInput{
@@ -208,8 +211,10 @@ func createCompleteVPC(ctx context.Context, awsClient *ec2.Client, args v1.Creat
208211

209212
func createPublicSubnet(ctx context.Context, awsClient *ec2.Client, vpc *types.Vpc, args v1.CreateSubnetArgs) (*types.Subnet, error) {
210213
tags := makeTags(map[string]string{
214+
"Name": fmt.Sprintf("%s-public", *vpc.VpcId),
211215
tagBrevVPCID: *vpc.VpcId,
212216
tagBrevSubnetType: string(args.Type),
217+
"CreatedBy": tagBrevCloudSDK,
213218
})
214219
input := &ec2.CreateSubnetInput{
215220
VpcId: vpc.VpcId,
@@ -280,6 +285,7 @@ func getOrCreatePublicRouteTable(ctx context.Context, awsClient *ec2.Client, vpc
280285
tags := makeTags(map[string]string{
281286
"Name": rtNameTag,
282287
tagBrevVPCID: *vpc.VpcId,
288+
"CreatedBy": tagBrevCloudSDK,
283289
})
284290
input := &ec2.CreateRouteTableInput{
285291
VpcId: aws.String(*vpc.VpcId),
@@ -348,7 +354,8 @@ func getOrCreateInternetGateway(ctx context.Context, awsClient *ec2.Client, vpc
348354

349355
// If there is no internet gateway, create one
350356
tags := makeTags(map[string]string{
351-
"Name": igwNameTag,
357+
"Name": igwNameTag,
358+
"CreatedBy": tagBrevCloudSDK,
352359
})
353360
input := &ec2.CreateInternetGatewayInput{
354361
TagSpecifications: []types.TagSpecification{
@@ -368,8 +375,10 @@ func getOrCreateInternetGateway(ctx context.Context, awsClient *ec2.Client, vpc
368375

369376
func createPrivateSubnet(ctx context.Context, awsClient *ec2.Client, vpc *types.Vpc, natGatewaySubnet *types.Subnet, args v1.CreateSubnetArgs) (*types.Subnet, error) {
370377
subnetTags := makeTags(map[string]string{
378+
"Name": fmt.Sprintf("%s-private", *vpc.VpcId),
371379
tagBrevVPCID: *vpc.VpcId,
372380
tagBrevSubnetType: string(args.Type),
381+
"CreatedBy": tagBrevCloudSDK,
373382
})
374383
createSubnetInput := &ec2.CreateSubnetInput{
375384
VpcId: vpc.VpcId,
@@ -397,6 +406,7 @@ func createPrivateSubnet(ctx context.Context, awsClient *ec2.Client, vpc *types.
397406
routeTableTags := makeTags(map[string]string{
398407
"Name": fmt.Sprintf("%s-private", *vpc.VpcId),
399408
tagBrevVPCID: *vpc.VpcId,
409+
"CreatedBy": tagBrevCloudSDK,
400410
})
401411
createRouteTableInput := &ec2.CreateRouteTableInput{
402412
VpcId: aws.String(*vpc.VpcId),
@@ -449,6 +459,7 @@ func createNatGateway(ctx context.Context, awsClient *ec2.Client, vpc *types.Vpc
449459
natGatewayTags := makeTags(map[string]string{
450460
"Name": fmt.Sprintf("%s-nat", *vpc.VpcId),
451461
tagBrevVPCID: *vpc.VpcId,
462+
"CreatedBy": tagBrevCloudSDK,
452463
})
453464
createNatGatewayInput := &ec2.CreateNatGatewayInput{
454465
SubnetId: subnet.SubnetId,
@@ -497,14 +508,14 @@ func (c *AWSClient) GetVPC(ctx context.Context, args v1.GetVPCArgs) (*v1.VPC, er
497508
return nil, err
498509
}
499510

500-
nameTag := ""
501-
refIDTag := ""
511+
brevVPCName := ""
512+
brevRefID := ""
502513
for _, tag := range awsVPC.Tags {
503514
if *tag.Key == "Name" {
504-
nameTag = *tag.Value
515+
brevVPCName = *tag.Value
505516
}
506517
if *tag.Key == tagBrevRefID {
507-
refIDTag = *tag.Value
518+
brevRefID = *tag.Value
508519
}
509520
}
510521

@@ -513,16 +524,22 @@ func (c *AWSClient) GetVPC(ctx context.Context, args v1.GetVPCArgs) (*v1.VPC, er
513524
return nil, err
514525
}
515526

527+
subnets, err := getVPCSubnets(ctx, awsClient, awsVPC)
528+
if err != nil {
529+
return nil, err
530+
}
531+
516532
return &v1.VPC{
517-
RefID: refIDTag,
518-
Name: nameTag,
533+
RefID: brevRefID,
534+
Name: brevVPCName,
519535
Location: args.Location,
520536
CloudID: *awsVPC.VpcId,
521537
CloudCredRefID: c.GetReferenceID(),
522538
Provider: CloudProviderID,
523539
Cloud: CloudProviderID,
524540
CidrBlock: *awsVPC.CidrBlock,
525541
Status: status,
542+
Subnets: subnets,
526543
}, nil
527544
}
528545

@@ -586,6 +603,42 @@ func getVPCStatus(ctx context.Context, awsClient *ec2.Client, awsVPC *types.Vpc)
586603
return v1.VPCStatusAvailable, nil
587604
}
588605

606+
func getVPCSubnets(ctx context.Context, awsClient *ec2.Client, awsVPC *types.Vpc) ([]v1.Subnet, error) {
607+
describeSubnetsOutput, err := awsClient.DescribeSubnets(ctx, &ec2.DescribeSubnetsInput{
608+
Filters: []types.Filter{
609+
{
610+
Name: aws.String("vpc-id"),
611+
Values: []string{*awsVPC.VpcId},
612+
},
613+
},
614+
})
615+
if err != nil {
616+
return nil, err
617+
}
618+
619+
subnets := make([]v1.Subnet, 0)
620+
for _, subnet := range describeSubnetsOutput.Subnets {
621+
var subnetType v1.SubnetType
622+
623+
// Get subnet type tag
624+
for _, tag := range subnet.Tags {
625+
if *tag.Key == tagBrevSubnetType {
626+
subnetType = v1.SubnetType(*tag.Value)
627+
break
628+
}
629+
}
630+
631+
subnets = append(subnets, v1.Subnet{
632+
CloudID: *subnet.SubnetId,
633+
VPCID: *awsVPC.VpcId,
634+
Location: *subnet.AvailabilityZone,
635+
CidrBlock: *subnet.CidrBlock,
636+
Type: subnetType,
637+
})
638+
}
639+
return subnets, nil
640+
}
641+
589642
func (c *AWSClient) DeleteVPC(ctx context.Context, args v1.DeleteVPCArgs) error {
590643
// Create the AWS client in the specified region
591644
awsClient := ec2.NewFromConfig(c.awsConfig, func(o *ec2.Options) {

v1/providers/aws/network_test.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ func TestCreateVPC(t *testing.T) {
5050
Location: location,
5151
CidrBlock: "10.0.0.0/16",
5252
Subnets: []v1.CreateSubnetArgs{
53-
{Name: "cloud-sdk-test-public-1", CidrBlock: "10.0.0.0/24", Type: v1.SubnetTypePublic},
54-
{Name: "cloud-sdk-test-private-1", CidrBlock: "10.0.1.0/24", Type: v1.SubnetTypePrivate},
55-
{Name: "cloud-sdk-test-public-2", CidrBlock: "10.0.2.0/24", Type: v1.SubnetTypePublic},
56-
{Name: "cloud-sdk-test-private-2", CidrBlock: "10.0.3.0/24", Type: v1.SubnetTypePrivate},
53+
{CidrBlock: "10.0.0.0/24", Type: v1.SubnetTypePublic},
54+
{CidrBlock: "10.0.1.0/24", Type: v1.SubnetTypePrivate},
55+
{CidrBlock: "10.0.2.0/24", Type: v1.SubnetTypePublic},
56+
{CidrBlock: "10.0.3.0/24", Type: v1.SubnetTypePrivate},
5757
},
5858
})
5959
if err != nil {
@@ -67,7 +67,6 @@ func TestCreateVPC(t *testing.T) {
6767
if err != nil {
6868
t.Fatalf("failed to get VPC: %v", err)
6969
}
70-
fmt.Println("VPC retrieved")
7170

7271
err = awsClient.DeleteVPC(context.Background(), v1.DeleteVPCArgs{
7372
VPC: &v1.VPC{
@@ -78,6 +77,4 @@ func TestCreateVPC(t *testing.T) {
7877
if err != nil {
7978
t.Fatalf("failed to delete VPC: %v", err)
8079
}
81-
82-
fmt.Println("VPC deleted")
8380
}

v1/providers/nebius/client.go

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,34 @@ package v1
22

33
import (
44
"context"
5+
"crypto/rsa"
6+
"crypto/x509"
7+
"encoding/base64"
8+
"encoding/pem"
59
"fmt"
610

711
v1 "github.com/brevdev/cloud/v1"
812
"github.com/nebius/gosdk"
13+
"github.com/nebius/gosdk/auth"
914
)
1015

1116
type NebiusCredential struct {
12-
RefID string
13-
ServiceAccountKey string // JSON service account key
14-
ProjectID string
17+
RefID string
18+
PublicKeyID string
19+
PrivateKeyPEMBase64 string
20+
ServiceAccountID string
21+
ProjectID string
1522
}
1623

1724
var _ v1.CloudCredential = &NebiusCredential{}
1825

19-
func NewNebiusCredential(refID, serviceAccountKey, projectID string) *NebiusCredential {
26+
func NewNebiusCredential(refID string, publicKeyID string, privateKeyPEMBase64 string, serviceAccountID string, projectID string) *NebiusCredential {
2027
return &NebiusCredential{
21-
RefID: refID,
22-
ServiceAccountKey: serviceAccountKey,
23-
ProjectID: projectID,
28+
RefID: refID,
29+
PublicKeyID: publicKeyID,
30+
PrivateKeyPEMBase64: privateKeyPEMBase64,
31+
ServiceAccountID: serviceAccountID,
32+
ProjectID: projectID,
2433
}
2534
}
2635

@@ -41,42 +50,64 @@ func (c *NebiusCredential) GetCloudProviderID() v1.CloudProviderID {
4150

4251
// GetTenantID returns the tenant ID for Nebius (project ID)
4352
func (c *NebiusCredential) GetTenantID() (string, error) {
44-
if c.ProjectID == "" {
45-
return "", fmt.Errorf("project ID is required for Nebius")
53+
if c.ServiceAccountID == "" {
54+
return "", fmt.Errorf("service account ID is required for Nebius")
4655
}
47-
return c.ProjectID, nil
56+
return c.ServiceAccountID, nil
4857
}
4958

50-
func (c *NebiusCredential) MakeClient(ctx context.Context, location string) (v1.CloudClient, error) {
51-
return NewNebiusClient(ctx, c.RefID, c.ServiceAccountKey, c.ProjectID, location)
59+
func (c *NebiusCredential) MakeClient(ctx context.Context, _ string) (v1.CloudClient, error) {
60+
return NewNebiusClient(ctx, c.RefID, c.PublicKeyID, c.PrivateKeyPEMBase64, c.ServiceAccountID, c.ProjectID)
5261
}
5362

5463
// It embeds NotImplCloudClient to handle unsupported features
5564
type NebiusClient struct {
5665
v1.NotImplCloudClient
57-
refID string
58-
serviceAccountKey string
59-
projectID string
60-
location string
61-
sdk *gosdk.SDK
66+
refID string
67+
projectID string
68+
sdk *gosdk.SDK
6269
}
6370

6471
var _ v1.CloudClient = &NebiusClient{}
6572

66-
func NewNebiusClient(ctx context.Context, refID, serviceAccountKey, projectID, location string) (*NebiusClient, error) {
73+
func NewNebiusClient(ctx context.Context, refID string, publicKeyID string, privateKeyPEMBase64 string, serviceAccountID string, projectID string) (*NebiusClient, error) {
74+
// Decode base64 into raw PEM bytes
75+
pemBytes, err := base64.StdEncoding.DecodeString(privateKeyPEMBase64)
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to base64 decode: %w", err)
78+
}
79+
80+
// Decode the PEM block
81+
block, _ := pem.Decode(pemBytes)
82+
if block == nil {
83+
return nil, fmt.Errorf("failed to parse PEM block")
84+
}
85+
86+
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
87+
if err != nil {
88+
return nil, fmt.Errorf("failed to parse PKCS8 private key: %w", err)
89+
}
90+
var ok bool
91+
privateKey, ok := parsedKey.(*rsa.PrivateKey)
92+
if !ok {
93+
return nil, fmt.Errorf("not an RSA private key")
94+
}
95+
6796
sdk, err := gosdk.New(ctx, gosdk.WithCredentials(
68-
gosdk.IAMToken(serviceAccountKey), // For now, treat as IAM token - will need proper service account handling later
97+
gosdk.ServiceAccount(auth.ServiceAccount{
98+
PrivateKey: privateKey,
99+
PublicKeyID: publicKeyID,
100+
ServiceAccountID: serviceAccountID,
101+
}),
69102
))
70103
if err != nil {
71104
return nil, fmt.Errorf("failed to initialize Nebius SDK: %w", err)
72105
}
73106

74107
return &NebiusClient{
75-
refID: refID,
76-
serviceAccountKey: serviceAccountKey,
77-
projectID: projectID,
78-
location: location,
79-
sdk: sdk,
108+
refID: refID,
109+
projectID: projectID,
110+
sdk: sdk,
80111
}, nil
81112
}
82113

@@ -90,11 +121,6 @@ func (c *NebiusClient) GetCloudProviderID() v1.CloudProviderID {
90121
return "nebius"
91122
}
92123

93-
// MakeClient creates a new client instance for a different location
94-
func (c *NebiusClient) MakeClient(ctx context.Context, location string) (v1.CloudClient, error) {
95-
return NewNebiusClient(ctx, c.refID, c.serviceAccountKey, c.projectID, location)
96-
}
97-
98124
// GetTenantID returns the tenant ID for Nebius
99125
func (c *NebiusClient) GetTenantID() (string, error) {
100126
return c.projectID, nil

0 commit comments

Comments
 (0)