From d8fd5d7d806da36c5cb27ea7b7beb72ff4d41d21 Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Tue, 2 Jul 2024 13:26:33 +0200 Subject: [PATCH 1/4] refactor(instance): extract functions from server create command --- .../instance/v1/custom_server_create.go | 262 +++------------ .../v1/custom_server_create_builder.go | 303 ++++++++++++++++++ 2 files changed, 344 insertions(+), 221 deletions(-) create mode 100644 internal/namespaces/instance/v1/custom_server_create_builder.go diff --git a/internal/namespaces/instance/v1/custom_server_create.go b/internal/namespaces/instance/v1/custom_server_create.go index f84167f816..a622b94f38 100644 --- a/internal/namespaces/instance/v1/custom_server_create.go +++ b/internal/namespaces/instance/v1/custom_server_create.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "net" "reflect" "strconv" "strings" @@ -193,245 +192,89 @@ func instanceWaitServerCreateRun() core.WaitFunc { } func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interface{}, e error) { + var err error args := argsI.(*instanceCreateServerRequest) // - // STEP 1: Argument validation and API requests creation. + // STEP 1: Argument handling and API requests creation. // - needIPCreation := false - - serverReq := &instance.CreateServerRequest{ - Zone: args.Zone, - Organization: args.OrganizationID, - Project: args.ProjectID, - Name: args.Name, - CommercialType: args.Type, - EnableIPv6: scw.BoolPtr(args.IPv6), - Tags: args.Tags, - RoutedIPEnabled: args.RoutedIPEnabled, - AdminPasswordEncryptionSSHKeyID: args.AdminPasswordEncryptionSSHKeyID, - } - client := core.ExtractClient(ctx) - apiMarketplace := marketplace.NewAPI(client) - apiInstance := instance.NewAPI(client) - if commercialTypeIsWindowsServer(serverReq.CommercialType) && serverReq.AdminPasswordEncryptionSSHKeyID == nil { - return nil, &core.CliError{ - Err: core.MissingRequiredArgumentError("admin-password-encryption-ssh-key-id").Err, - Details: "Expected a SSH Key ID to encrypt Admin RDP password. If not provided, no password will be generated. Key must be RSA Public Key.", - Hint: "Use completion or get your ssh key id using 'scw iam ssh-key list',", - Code: 1, - Empty: false, - } - } - - // - // Image. - // - // Could be: - // - A local image UUID - // - An image label - // - switch { - case args.Image == "none": - break - case !validation.IsUUID(args.Image): - // For retro-compatibility, we replace dashes with underscores - imageLabel := strings.Replace(args.Image, "-", "_", -1) - - // Find the corresponding local image UUID. - localImage, err := apiMarketplace.GetLocalImageByLabel(&marketplace.GetLocalImageByLabelRequest{ - ImageLabel: imageLabel, - Zone: args.Zone, - CommercialType: serverReq.CommercialType, - Type: marketplace.LocalImageTypeInstanceLocal, - }) - if err != nil { - return nil, err - } - serverReq.Image = localImage.ID - default: - serverReq.Image = args.Image - } - - var ( - getImageResponse *instance.GetImageResponse - serverType *instance.ServerType - ) - if args.Image != "none" { - var err error - getImageResponse, err = apiInstance.GetImage(&instance.GetImageRequest{ - Zone: args.Zone, - ImageID: serverReq.Image, - }) - if err != nil { - logger.Warningf("cannot get image %s: %s", serverReq.Image, err) - } - - serverType = getServerType(apiInstance, serverReq.Zone, serverReq.CommercialType) - - if serverType != nil && getImageResponse != nil { - if err := validateImageServerTypeCompatibility(getImageResponse.Image, serverType, serverReq.CommercialType); err != nil { - return nil, err - } - } else { - logger.Warningf("skipping image server-type compatibility validation") - } - } else { - getImageResponse = nil - serverType = nil + serverBuilder := NewServerBuilder(client, args.Name, args.Zone, args.Type). + AddOrganizationID(args.OrganizationID). + AddProjectID(args.ProjectID). + AddEnableIPv6(scw.BoolPtr(args.IPv6)). + AddTags(args.Tags). + AddRoutedIPEnabled(args.RoutedIPEnabled). + AddAdminPasswordEncryptionSSHKeyID(args.AdminPasswordEncryptionSSHKeyID). + AddBootType(args.BootType). + AddSecurityGroup(args.SecurityGroupID). + AddPlacementGroup(args.PlacementGroupID) + + serverBuilder, err = serverBuilder.AddImage(args.Image) + if err != nil { + return nil, err } - // - // IP. - // - // Could be: - // - "new" - // - A flexible IP UUID - // - A flexible IP address - // - "dynamic" - // - "none" - // - switch { - case args.IP == "", args.IP == "new": - needIPCreation = true - case validation.IsUUID(args.IP): - serverReq.PublicIP = scw.StringPtr(args.IP) - case net.ParseIP(args.IP) != nil: - // Find the corresponding flexible IP UUID. - logger.Debugf("finding public IP UUID from address: %s", args.IP) - res, err := apiInstance.GetIP(&instance.GetIPRequest{ - Zone: args.Zone, - IP: args.IP, - }) - if err != nil { // FIXME: isNotFoundError - return nil, fmt.Errorf("%s does not belong to you", args.IP) - } - serverReq.PublicIP = scw.StringPtr(res.IP.ID) - case args.IP == "dynamic": - serverReq.DynamicIPRequired = scw.BoolPtr(true) - case args.IP == "none": - serverReq.DynamicIPRequired = scw.BoolPtr(false) - default: - return nil, fmt.Errorf(`invalid IP "%s", should be either 'new', 'dynamic', 'none', an IP address ID or a reserved flexible IP address`, args.IP) + serverBuilder, err = serverBuilder.AddIP(args.IP) + if err != nil { + return nil, err } - // - // Volumes. - // - // More format details in buildVolumeTemplate function. - // - if len(args.AdditionalVolumes) > 0 || args.RootVolume != "" { - // Create initial volume template map. - volumes, err := buildVolumes(apiInstance, args.Zone, serverReq.Name, args.RootVolume, args.AdditionalVolumes) - if err != nil { - return nil, err - } - - // Validate root volume type and size. - if args.Image != "none" && getImageResponse != nil { - if err := validateRootVolume(getImageResponse.Image.RootVolume.Size, volumes["0"]); err != nil { - return nil, err - } - } else { - logger.Warningf("skipping root volume validation") - } - - // Validate total local volume sizes. - if args.Image != "none" && serverType != nil && getImageResponse != nil { - if err := validateLocalVolumeSizes(volumes, serverType, serverReq.CommercialType, getImageResponse.Image.RootVolume.Size); err != nil { - return nil, err - } - } else { - logger.Warningf("skip local volume size validation") - } - - // Sanitize the volume map to respect API schemas - serverReq.Volumes = sanitizeVolumeMap(serverReq.Name, volumes) + serverBuilder, err = serverBuilder.AddVolumes(args.RootVolume, args.AdditionalVolumes) + if err != nil { + return nil, err } - // Add default volumes to server, ex: scratch storage for GPU servers - if serverType != nil { - serverReq.Volumes = addDefaultVolumes(serverType, serverReq.Volumes) + serverBuilder, err = serverBuilder.AddBootscript(args.BootscriptID) + if err != nil { + return nil, err } // - // BootType. + // STEP 2: Validation and requests // - bootType := instance.BootType(args.BootType) - serverReq.BootType = &bootType - // - // Bootscript. - // - if args.BootscriptID != "" { - if !validation.IsUUID(args.BootscriptID) { - return nil, fmt.Errorf("bootscript ID %s is not a valid UUID", args.BootscriptID) - } - //nolint: staticcheck // Bootscript is deprecated - _, err := apiInstance.GetBootscript(&instance.GetBootscriptRequest{ - Zone: args.Zone, - BootscriptID: args.BootscriptID, - }) - if err != nil { // FIXME: isNotFoundError - return nil, fmt.Errorf("bootscript ID %s does not exist", args.BootscriptID) - } - - //nolint: staticcheck // Bootscript is deprecated - serverReq.Bootscript = scw.StringPtr(args.BootscriptID) - bootType := instance.BootTypeBootscript - serverReq.BootType = &bootType - } - - // - // Security Group. - // - if args.SecurityGroupID != "" { - serverReq.SecurityGroup = scw.StringPtr(args.SecurityGroupID) + err = serverBuilder.Validate() + if err != nil { + return nil, err } - // - // Placement Group. - // - if args.PlacementGroupID != "" { - serverReq.PlacementGroup = scw.StringPtr(args.PlacementGroupID) - } + createReq, createIPReq := serverBuilder.Build() + needIPCreation := createIPReq != nil // - // STEP 2: Resource creations and modifications. + // IP creation // + apiInstance := instance.NewAPI(client) - // - // IP - // if needIPCreation { logger.Debugf("creating IP") - ip, err := instanceServerCreateIPCreate(args, apiInstance) + ipRes, err := apiInstance.CreateIP(createIPReq) if err != nil { return nil, fmt.Errorf("error while creating your public IP: %s", err) } - serverReq.PublicIP = scw.StringPtr(ip.ID) - logger.Debugf("IP created: %s", serverReq.PublicIP) + createReq.PublicIP = scw.StringPtr(ipRes.IP.ID) + logger.Debugf("IP created: %s", createReq.PublicIP) } // // Server Creation // logger.Debugf("creating server") - serverRes, err := apiInstance.CreateServer(serverReq) + serverRes, err := apiInstance.CreateServer(createReq) if err != nil { - if needIPCreation && serverReq.PublicIP != nil { + if needIPCreation && createReq.PublicIP != nil { // Delete the created IP - logger.Debugf("deleting created IP: %s", serverReq.PublicIP) + logger.Debugf("deleting created IP: %s", createReq.PublicIP) err := apiInstance.DeleteIP(&instance.DeleteIPRequest{ Zone: args.Zone, - IP: *serverReq.PublicIP, + IP: *createReq.PublicIP, }) if err != nil { - logger.Warningf("cannot delete the create IP %s: %s.", serverReq.PublicIP, err) + logger.Warningf("cannot delete the create IP %s: %s.", createReq.PublicIP, err) } } @@ -805,26 +648,3 @@ func getServerType(apiInstance *instance.API, zone scw.Zone, commercialType stri return serverType } - -func instanceServerCreateIPCreate(args *instanceCreateServerRequest, api *instance.API) (*instance.IP, error) { - req := &instance.CreateIPRequest{ - Zone: args.Zone, - Project: args.ProjectID, - Organization: args.OrganizationID, - } - - if args.RoutedIPEnabled != nil { - if *args.RoutedIPEnabled { - req.Type = instance.IPTypeRoutedIPv4 - } else { - req.Type = instance.IPTypeNat - } - } - - res, err := api.CreateIP(req) - if err != nil { - return nil, err - } - - return res.IP, nil -} diff --git a/internal/namespaces/instance/v1/custom_server_create_builder.go b/internal/namespaces/instance/v1/custom_server_create_builder.go new file mode 100644 index 0000000000..b436ac1503 --- /dev/null +++ b/internal/namespaces/instance/v1/custom_server_create_builder.go @@ -0,0 +1,303 @@ +package instance + +import ( + "fmt" + "net" + "strings" + + "github.com/scaleway/scaleway-cli/v2/internal/core" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/api/marketplace/v2" + "github.com/scaleway/scaleway-sdk-go/logger" + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/scaleway/scaleway-sdk-go/validation" +) + +type ServerBuilder struct { + // createdReq is the request being built + createReq *instance.CreateServerRequest + // createIPReq is filled with a request if an IP is needed + createIPReq *instance.CreateIPRequest + + // All needed APIs + apiMarketplace *marketplace.API + apiInstance *instance.API + + // serverType is filled with the ServerType if CommercialType is found in the API. + serverType *instance.ServerType + // serverImage is filled with the Image if one is provided + serverImage *instance.Image +} + +func NewServerBuilder(client *scw.Client, name string, zone scw.Zone, commercialType string) *ServerBuilder { + sb := &ServerBuilder{ + createReq: &instance.CreateServerRequest{ + Name: name, + Zone: zone, + CommercialType: commercialType, + }, + apiMarketplace: marketplace.NewAPI(client), + apiInstance: instance.NewAPI(client), + } + + return sb +} + +func (sb *ServerBuilder) AddOrganizationID(orgID *string) *ServerBuilder { + if orgID != nil { + sb.createReq.Organization = orgID + } + + return sb +} + +func (sb *ServerBuilder) AddProjectID(projectID *string) *ServerBuilder { + if projectID != nil { + sb.createReq.Project = projectID + } + + return sb +} + +func (sb *ServerBuilder) AddEnableIPv6(enableIPv6 *bool) *ServerBuilder { + if enableIPv6 != nil { + sb.createReq.EnableIPv6 = enableIPv6 //nolint: staticcheck + } + + return sb +} + +func (sb *ServerBuilder) AddTags(tags []string) *ServerBuilder { + if len(tags) > 0 { + sb.createReq.Tags = tags + } + + return sb +} + +func (sb *ServerBuilder) AddRoutedIPEnabled(routedIPEnabled *bool) *ServerBuilder { + if routedIPEnabled != nil { + sb.createReq.RoutedIPEnabled = routedIPEnabled + } + + return sb +} + +func (sb *ServerBuilder) AddAdminPasswordEncryptionSSHKeyID(adminPasswordEncryptionSSHKeyID *string) *ServerBuilder { + if adminPasswordEncryptionSSHKeyID != nil { + sb.createReq.AdminPasswordEncryptionSSHKeyID = adminPasswordEncryptionSSHKeyID + } + + return sb +} + +func (sb *ServerBuilder) isWindows() bool { + return commercialTypeIsWindowsServer(sb.createReq.CommercialType) +} + +// defaultIPType returns the default IP type when created by the CLI. Used for ServerBuilder.AddIP +func (sb *ServerBuilder) defaultIPType() instance.IPType { + if sb.createReq.RoutedIPEnabled != nil { + if *sb.createReq.RoutedIPEnabled { + return instance.IPTypeRoutedIPv4 + } + return instance.IPTypeNat + } + + return "" +} + +// AddImage handle a custom image argument. +// image could be: +// - A local image UUID. +// - An image label. +func (sb *ServerBuilder) AddImage(image string) (*ServerBuilder, error) { + switch { + case image == "none": + return sb, nil + case !validation.IsUUID(image): + imageLabel := strings.Replace(image, "-", "_", -1) + + localImage, err := sb.apiMarketplace.GetLocalImageByLabel(&marketplace.GetLocalImageByLabelRequest{ + ImageLabel: imageLabel, + Zone: sb.createReq.Zone, + CommercialType: sb.createReq.CommercialType, + Type: marketplace.LocalImageTypeInstanceLocal, + }) + if err != nil { + return sb, err + } + + image = localImage.ID + } + + sb.createReq.Image = image + + getImageResponse, err := sb.apiInstance.GetImage(&instance.GetImageRequest{ + Zone: sb.createReq.Zone, + ImageID: sb.createReq.Image, + }) + if err != nil { + logger.Warningf("cannot get image %s: %s", sb.createReq.Image, err) + } else { + sb.serverImage = getImageResponse.Image + } + + sb.serverType = getServerType(sb.apiInstance, sb.createReq.Zone, sb.createReq.CommercialType) + + return sb, nil +} + +// AddIP takes an ip argument and change requests accordingly. +// ip could be: +// - "new" +// - A flexible IP UUID +// - A flexible IP address +// - "dynamic" +// - "none" +func (sb *ServerBuilder) AddIP(ip string) (*ServerBuilder, error) { + switch { + case ip == "" || ip == "new": + sb.createIPReq = &instance.CreateIPRequest{ + Zone: sb.createReq.Zone, + Organization: sb.createReq.Project, + Project: sb.createReq.Project, + Type: sb.defaultIPType(), + } + case validation.IsUUID(ip): + sb.createReq.PublicIP = scw.StringPtr(ip) + case net.ParseIP(ip) != nil: + logger.Debugf("finding public IP UUID from address: %s", ip) + res, err := sb.apiInstance.GetIP(&instance.GetIPRequest{ + Zone: sb.createReq.Zone, + IP: ip, + }) + if err != nil { // FIXME: isNotFoundError + return sb, fmt.Errorf("%s does not belong to you", ip) + } + sb.createReq.PublicIP = scw.StringPtr(res.IP.ID) + + case ip == "dynamic": + sb.createReq.DynamicIPRequired = scw.BoolPtr(true) + case ip == "none": + sb.createReq.DynamicIPRequired = scw.BoolPtr(false) + default: + return sb, fmt.Errorf(`invalid IP "%s", should be either 'new', 'dynamic', 'none', an IP address ID or a reserved flexible IP address`, ip) + } + + return sb, nil +} + +// AddVolumes build volume templates from arguments. +// +// More format details in buildVolumeTemplate function. +// +// Also add default volumes to server, ex: scratch storage for GPU servers +func (sb *ServerBuilder) AddVolumes(rootVolume string, additionalVolumes []string) (*ServerBuilder, error) { + if len(additionalVolumes) > 0 || rootVolume != "" { + // Create initial volume template map. + volumes, err := buildVolumes(sb.apiInstance, sb.createReq.Zone, sb.createReq.Name, rootVolume, additionalVolumes) + if err != nil { + return sb, err + } + + // Validate root volume type and size. + if sb.serverImage != nil { + if err := validateRootVolume(sb.serverImage.RootVolume.Size, volumes["0"]); err != nil { + return sb, err + } + } else { + logger.Warningf("skipping root volume validation") + } + + // Validate total local volume sizes. + if sb.serverType != nil && sb.serverImage != nil { + if err := validateLocalVolumeSizes(volumes, sb.serverType, sb.createReq.CommercialType, sb.serverImage.RootVolume.Size); err != nil { + return sb, err + } + } else { + logger.Warningf("skip local volume size validation") + } + + // Sanitize the volume map to respect API schemas + sb.createReq.Volumes = sanitizeVolumeMap(sb.createReq.Name, volumes) + } + + if sb.serverType != nil { + sb.createReq.Volumes = addDefaultVolumes(sb.serverType, sb.createReq.Volumes) + } + + return sb, nil +} + +func (sb *ServerBuilder) AddBootType(bootType string) *ServerBuilder { + instanceBootType := instance.BootType(bootType) + sb.createReq.BootType = &instanceBootType + + return sb +} + +func (sb *ServerBuilder) AddBootscript(bootscriptID string) (*ServerBuilder, error) { + if bootscriptID != "" { + if !validation.IsUUID(bootscriptID) { + return nil, fmt.Errorf("bootscript ID %s is not a valid UUID", bootscriptID) + } + //nolint: staticcheck // Bootscript is deprecated + _, err := sb.apiInstance.GetBootscript(&instance.GetBootscriptRequest{ + Zone: sb.createReq.Zone, + BootscriptID: bootscriptID, + }) + if err != nil { // FIXME: isNotFoundError + return nil, fmt.Errorf("bootscript ID %s does not exist", bootscriptID) + } + + //nolint: staticcheck // Bootscript is deprecated + sb.createReq.Bootscript = scw.StringPtr(bootscriptID) + bootType := instance.BootTypeBootscript + sb.createReq.BootType = &bootType + } + + return sb, nil +} + +func (sb *ServerBuilder) AddSecurityGroup(securityGroupID string) *ServerBuilder { + if securityGroupID != "" { + sb.createReq.SecurityGroup = &securityGroupID + } + + return sb +} + +func (sb *ServerBuilder) AddPlacementGroup(placementGroupID string) *ServerBuilder { + if placementGroupID != "" { + sb.createReq.PlacementGroup = &placementGroupID + } + + return sb +} + +func (sb *ServerBuilder) Validate() error { + if sb.isWindows() && sb.createReq.AdminPasswordEncryptionSSHKeyID == nil { + return &core.CliError{ + Err: core.MissingRequiredArgumentError("admin-password-encryption-ssh-key-id").Err, + Details: "Expected a SSH Key ID to encrypt Admin RDP password. If not provided, no password will be generated. Key must be RSA Public Key.", + Hint: "Use completion or get your ssh key id using 'scw iam ssh-key list',", + Code: 1, + Empty: false, + } + } + + if sb.serverType != nil && sb.serverImage != nil { + if err := validateImageServerTypeCompatibility(sb.serverImage, sb.serverType, sb.createReq.CommercialType); err != nil { + return err + } + } else { + logger.Warningf("skipping image server-type compatibility validation") + } + + return nil +} + +func (sb *ServerBuilder) Build() (*instance.CreateServerRequest, *instance.CreateIPRequest) { + return sb.createReq, sb.createIPReq +} From 1fc16a93df7be4a3c7040295cd17fad626906ebe Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Wed, 3 Jul 2024 13:48:06 +0200 Subject: [PATCH 2/4] fix crash in test and update builder method --- .../namespaces/instance/v1/custom_server_create_builder.go | 4 +--- internal/namespaces/instance/v1/custom_server_create_test.go | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/namespaces/instance/v1/custom_server_create_builder.go b/internal/namespaces/instance/v1/custom_server_create_builder.go index b436ac1503..1204399da4 100644 --- a/internal/namespaces/instance/v1/custom_server_create_builder.go +++ b/internal/namespaces/instance/v1/custom_server_create_builder.go @@ -60,9 +60,7 @@ func (sb *ServerBuilder) AddProjectID(projectID *string) *ServerBuilder { } func (sb *ServerBuilder) AddEnableIPv6(enableIPv6 *bool) *ServerBuilder { - if enableIPv6 != nil { - sb.createReq.EnableIPv6 = enableIPv6 //nolint: staticcheck - } + sb.createReq.EnableIPv6 = enableIPv6 //nolint: staticcheck return sb } diff --git a/internal/namespaces/instance/v1/custom_server_create_test.go b/internal/namespaces/instance/v1/custom_server_create_test.go index 8632404772..e1f40fef35 100644 --- a/internal/namespaces/instance/v1/custom_server_create_test.go +++ b/internal/namespaces/instance/v1/custom_server_create_test.go @@ -278,6 +278,7 @@ func Test_CreateServer(t *testing.T) { Check: core.TestCheckCombine( func(t *testing.T, ctx *core.CheckFuncCtx) { assert.NotNil(t, ctx.Result) + assert.NotNil(t, ctx.Result.(*instanceSDK.Server).IPv6) assert.NotEmpty(t, ctx.Result.(*instanceSDK.Server).IPv6.Address) }, core.TestCheckExitCode(0), From 6910e2a1d801c9c633f0c47fa459c51e67e56dd2 Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Wed, 3 Jul 2024 14:05:42 +0200 Subject: [PATCH 3/4] fix test --- internal/namespaces/instance/v1/custom_server_create.go | 2 +- internal/namespaces/instance/v1/custom_server_create_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/namespaces/instance/v1/custom_server_create.go b/internal/namespaces/instance/v1/custom_server_create.go index a622b94f38..ea37f11e9f 100644 --- a/internal/namespaces/instance/v1/custom_server_create.go +++ b/internal/namespaces/instance/v1/custom_server_create.go @@ -98,7 +98,7 @@ func serverCreateCommand() *core.Command { }, { Name: "ipv6", - Short: "Enable IPv6", + Short: "Enable IPv6, to be used with routed-ip-enabled=false", }, { Name: "stopped", diff --git a/internal/namespaces/instance/v1/custom_server_create_test.go b/internal/namespaces/instance/v1/custom_server_create_test.go index e1f40fef35..2f173dcb41 100644 --- a/internal/namespaces/instance/v1/custom_server_create_test.go +++ b/internal/namespaces/instance/v1/custom_server_create_test.go @@ -274,7 +274,7 @@ func Test_CreateServer(t *testing.T) { t.Run("with ipv6", core.Test(&core.TestConfig{ Commands: instance.GetCommands(), - Cmd: "scw instance server create image=ubuntu_bionic ipv6=true -w", // IPv6 is created at runtime + Cmd: "scw instance server create image=ubuntu_bionic routed-ip-enabled=false ipv6=true -w", // IPv6 is created at runtime Check: core.TestCheckCombine( func(t *testing.T, ctx *core.CheckFuncCtx) { assert.NotNil(t, ctx.Result) From e09e2ddaa75300c613e325bfdfc02e909693bcf6 Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Wed, 3 Jul 2024 14:08:06 +0200 Subject: [PATCH 4/4] update doc --- .../testdata/test-all-usage-instance-server-create-usage.golden | 2 +- docs/commands/instance.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/scw/testdata/test-all-usage-instance-server-create-usage.golden b/cmd/scw/testdata/test-all-usage-instance-server-create-usage.golden index 266a5c06c3..066b2b9a64 100644 --- a/cmd/scw/testdata/test-all-usage-instance-server-create-usage.golden +++ b/cmd/scw/testdata/test-all-usage-instance-server-create-usage.golden @@ -36,7 +36,7 @@ ARGS: [additional-volumes.{index}] Additional local and block volumes attached to your server [ip=new] Either an IP, an IP ID, 'new' to create a new IP, 'dynamic' to use a dynamic IP or 'none' for no public IP (new | dynamic | none | |
) [tags.{index}] Server tags - [ipv6] Enable IPv6 + [ipv6] Enable IPv6, to be used with routed-ip-enabled=false [stopped] Do not start server after its creation [security-group-id] The security group ID used for this server [placement-group-id] The placement group ID in which the server has to be created diff --git a/docs/commands/instance.md b/docs/commands/instance.md index 7b9701979c..f2a8598fb3 100644 --- a/docs/commands/instance.md +++ b/docs/commands/instance.md @@ -1712,7 +1712,7 @@ scw instance server create [arg=value ...] | additional-volumes.{index} | | Additional local and block volumes attached to your server | | ip | Default: `new` | Either an IP, an IP ID, 'new' to create a new IP, 'dynamic' to use a dynamic IP or 'none' for no public IP (new | dynamic | none | |
) | | tags.{index} | | Server tags | -| ipv6 | | Enable IPv6 | +| ipv6 | | Enable IPv6, to be used with routed-ip-enabled=false | | stopped | | Do not start server after its creation | | security-group-id | | The security group ID used for this server | | placement-group-id | | The placement group ID in which the server has to be created |