Skip to content

Improve public IPs management #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions internal/service/scaleway/client/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type InstanceAPI interface {
DeleteServer(req *instance.DeleteServerRequest, opts ...scw.RequestOption) error
ListPlacementGroups(req *instance.ListPlacementGroupsRequest, opts ...scw.RequestOption) (*instance.ListPlacementGroupsResponse, error)
ListSecurityGroups(req *instance.ListSecurityGroupsRequest, opts ...scw.RequestOption) (*instance.ListSecurityGroupsResponse, error)
UpdateServer(req *instance.UpdateServerRequest, opts ...scw.RequestOption) (*instance.UpdateServerResponse, error)
}

type Instance interface {
Expand All @@ -44,7 +45,7 @@ type Instance interface {
placementGroupID, securityGroupID *string,
rootVolumeSize scw.Size,
rootVolumeType instance.VolumeVolumeType,
publicIPs, tags []string,
tags []string,
) (*instance.Server, error)
FindImage(ctx context.Context, zone scw.Zone, name string) (*instance.Image, error)
FindIPs(ctx context.Context, zone scw.Zone, tags []string) ([]*instance.IP, error)
Expand All @@ -62,6 +63,7 @@ type Instance interface {
DeleteServer(ctx context.Context, zone scw.Zone, serverID string) error
FindPlacementGroup(ctx context.Context, zone scw.Zone, name string) (*instance.PlacementGroup, error)
FindSecurityGroup(ctx context.Context, zone scw.Zone, name string) (*instance.SecurityGroup, error)
UpdateServerPublicIPs(ctx context.Context, zone scw.Zone, id string, publicIPIDs []string) (*instance.Server, error)
}

// FindServer finds an existing Instance server by tags.
Expand Down Expand Up @@ -106,7 +108,7 @@ func (c *Client) CreateServer(
placementGroupID, securityGroupID *string,
rootVolumeSize scw.Size,
rootVolumeType instance.VolumeVolumeType,
publicIPs, tags []string,
tags []string,
) (*instance.Server, error) {
if err := c.validateZone(c.instance, zone); err != nil {
return nil, err
Expand Down Expand Up @@ -138,10 +140,6 @@ func (c *Client) CreateServer(
Tags: append(tags, createdByTag),
}

if len(publicIPs) > 0 {
req.PublicIPs = &publicIPs
}

// Automatically attach scratch volume if server supports it.
if serverType.ScratchStorageMaxSize != nil && *serverType.ScratchStorageMaxSize > 0 {
req.Volumes["1"] = &instance.VolumeServerTemplate{
Expand Down Expand Up @@ -482,3 +480,20 @@ func (c *Client) FindSecurityGroup(ctx context.Context, zone scw.Zone, name stri
return nil, fmt.Errorf("%w: found %d security groups with name %s", ErrTooManyItemsFound, len(securityGroups), name)
}
}

func (c *Client) UpdateServerPublicIPs(ctx context.Context, zone scw.Zone, id string, publicIPIDs []string) (*instance.Server, error) {
if err := c.validateZone(c.instance, zone); err != nil {
return nil, err
}

resp, err := c.instance.UpdateServer(&instance.UpdateServerRequest{
Zone: zone,
ServerID: id,
PublicIPs: &publicIPIDs,
}, scw.WithContext(ctx))
if err != nil {
return nil, newCallError("UpdateServer", err)
}

return resp.Server, nil
}
86 changes: 81 additions & 5 deletions internal/service/scaleway/client/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ func TestClient_CreateServer(t *testing.T) {
securityGroupID *string
rootVolumeSize scw.Size
rootVolumeType instance.VolumeVolumeType
publicIPs []string
tags []string
}
tests := []struct {
Expand All @@ -203,7 +202,6 @@ func TestClient_CreateServer(t *testing.T) {
securityGroupID: scw.StringPtr(securityGroupID),
rootVolumeSize: rootVolumeSize,
rootVolumeType: instance.VolumeVolumeTypeBSSD,
publicIPs: []string{"42.42.42.42"},
tags: []string{"tag1", "tag2", "tag3"},
},
expect: func(d *mock_client.MockInstanceAPIMockRecorder) {
Expand All @@ -227,8 +225,7 @@ func TestClient_CreateServer(t *testing.T) {
Boot: scw.BoolPtr(true),
},
},
Tags: []string{"tag1", "tag2", "tag3", createdByTag},
PublicIPs: &[]string{"42.42.42.42"},
Tags: []string{"tag1", "tag2", "tag3", createdByTag},
}, gomock.Any()).Return(&instance.CreateServerResponse{
Server: &instance.Server{
Name: "server",
Expand Down Expand Up @@ -316,7 +313,7 @@ func TestClient_CreateServer(t *testing.T) {
region: tt.fields.region,
instance: instanceMock,
}
got, err := c.CreateServer(tt.args.ctx, tt.args.zone, tt.args.name, tt.args.commercialType, tt.args.imageID, tt.args.placementGroupID, tt.args.securityGroupID, tt.args.rootVolumeSize, tt.args.rootVolumeType, tt.args.publicIPs, tt.args.tags)
got, err := c.CreateServer(tt.args.ctx, tt.args.zone, tt.args.name, tt.args.commercialType, tt.args.imageID, tt.args.placementGroupID, tt.args.securityGroupID, tt.args.rootVolumeSize, tt.args.rootVolumeType, tt.args.tags)
if (err != nil) != tt.wantErr {
t.Errorf("Client.CreateServer() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -1731,3 +1728,82 @@ func TestClient_FindSecurityGroup(t *testing.T) {
})
}
}

func TestClient_UpdateServerPublicIPs(t *testing.T) {
t.Parallel()
type fields struct {
projectID string
region scw.Region
}
type args struct {
ctx context.Context
zone scw.Zone
id string
publicIPIDs []string
}
tests := []struct {
name string
fields fields
args args
want *instance.Server
wantErr bool
expect func(d *mock_client.MockInstanceAPIMockRecorder)
}{
{
name: "update public IPs",
fields: fields{
projectID: projectID,
region: scw.RegionFrPar,
},
args: args{
ctx: context.TODO(),
zone: scw.ZoneFrPar1,
id: serverID,
publicIPIDs: []string{ipID},
},
want: &instance.Server{
ID: serverID,
},
expect: func(d *mock_client.MockInstanceAPIMockRecorder) {
d.UpdateServer(&instance.UpdateServerRequest{
Zone: scw.ZoneFrPar1,
ServerID: serverID,
PublicIPs: &[]string{ipID},
}, gomock.Any()).Return(&instance.UpdateServerResponse{
Server: &instance.Server{
ID: serverID,
},
}, nil)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

instanceMock := mock_client.NewMockInstanceAPI(mockCtrl)

// Every API call must be preceded by a zone check.
instanceMock.EXPECT().Zones().Return(tt.fields.region.GetZones())

tt.expect(instanceMock.EXPECT())

c := &Client{
projectID: tt.fields.projectID,
region: tt.fields.region,
instance: instanceMock,
}
got, err := c.UpdateServerPublicIPs(tt.args.ctx, tt.args.zone, tt.args.id, tt.args.publicIPIDs)
if (err != nil) != tt.wantErr {
t.Errorf("Client.UpdateServerPublicIPs() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.UpdateServerPublicIPs() = %v, want %v", got, tt.want)
}
})
}
}
51 changes: 45 additions & 6 deletions internal/service/scaleway/client/mock_client/client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading