From 03ccdd9e44b3d6dc96c745e532bc746b6452136a Mon Sep 17 00:00:00 2001 From: Quentin Brosse Date: Tue, 8 Sep 2020 19:15:28 +0200 Subject: [PATCH] feat(core): deprecate an argument --- ...l-usage-instance-image-create-usage.golden | 2 +- ...-all-usage-instance-ip-create-usage.golden | 2 +- ...stance-placement-group-create-usage.golden | 2 +- ...nstance-security-group-create-usage.golden | 2 +- ...-usage-instance-server-create-usage.golden | 5 +- ...sage-instance-snapshot-create-usage.golden | 2 +- ...-usage-instance-volume-create-usage.golden | 2 +- go.sum | 32 +--------- internal/core/arg_specs.go | 22 ++++++- internal/core/autocomplete.go | 5 ++ internal/core/cobra_builder.go | 11 +++- internal/core/cobra_usage_builder.go | 13 ++++- internal/core/cobra_usage_builder_test.go | 2 +- internal/core/cobra_utils.go | 6 +- internal/core/context.go | 9 +++ internal/core/validate.go | 37 +++++++++++- internal/core/validate_test.go | 33 ++++++++++- internal/docgen/docgen.go | 10 ++++ internal/docgen/tpl.go | 2 +- .../instance/v1/custom_server_create.go | 58 ++++++++++++------- 20 files changed, 185 insertions(+), 72 deletions(-) diff --git a/cmd/scw/testdata/test-all-usage-instance-image-create-usage.golden b/cmd/scw/testdata/test-all-usage-instance-image-create-usage.golden index 563f71b8bf..6977578ad0 100644 --- a/cmd/scw/testdata/test-all-usage-instance-image-create-usage.golden +++ b/cmd/scw/testdata/test-all-usage-instance-image-create-usage.golden @@ -22,7 +22,7 @@ ARGS: [additional-snapshots.{key}.organization-id] Organization ID that own the additional snapshot [project] Project ID of the image [public] True to create a public image - [organization-id] Organization ID to use. If none is passed will use default organization ID from the config + [organization-id] Organization ID to use. If none is passed the default organization ID will be used [zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | nl-ams-1) FLAGS: diff --git a/cmd/scw/testdata/test-all-usage-instance-ip-create-usage.golden b/cmd/scw/testdata/test-all-usage-instance-ip-create-usage.golden index 241579b589..d1da3dde13 100644 --- a/cmd/scw/testdata/test-all-usage-instance-ip-create-usage.golden +++ b/cmd/scw/testdata/test-all-usage-instance-ip-create-usage.golden @@ -19,7 +19,7 @@ ARGS: [project-id] The project ID the IP is reserved in [server] UUID of the server you want to attach the IP to [tags.{index}] An array of keywords you want to tag this IP with - [organization-id] Organization ID to use. If none is passed will use default organization ID from the config + [organization-id] Organization ID to use. If none is passed the default organization ID will be used [zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | nl-ams-1) FLAGS: diff --git a/cmd/scw/testdata/test-all-usage-instance-placement-group-create-usage.golden b/cmd/scw/testdata/test-all-usage-instance-placement-group-create-usage.golden index 1ffd859979..58501f8e14 100644 --- a/cmd/scw/testdata/test-all-usage-instance-placement-group-create-usage.golden +++ b/cmd/scw/testdata/test-all-usage-instance-placement-group-create-usage.golden @@ -29,7 +29,7 @@ ARGS: [project] [policy-mode] (optional | enforced) [policy-type] (max_availability | low_latency) - [organization-id] Organization ID to use. If none is passed will use default organization ID from the config + [organization-id] Organization ID to use. If none is passed the default organization ID will be used [zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | nl-ams-1) FLAGS: diff --git a/cmd/scw/testdata/test-all-usage-instance-security-group-create-usage.golden b/cmd/scw/testdata/test-all-usage-instance-security-group-create-usage.golden index 2922f694b9..60f4256719 100644 --- a/cmd/scw/testdata/test-all-usage-instance-security-group-create-usage.golden +++ b/cmd/scw/testdata/test-all-usage-instance-security-group-create-usage.golden @@ -30,7 +30,7 @@ ARGS: [stateful=true] Whether the security group is stateful or not [inbound-default-policy=accept] Default policy for inbound rules (accept | drop) [outbound-default-policy=accept] Default policy for outbound rules (accept | drop) - [organization-id] Organization ID to use. If none is passed will use default organization ID from the config + [organization-id] Organization ID to use. If none is passed the default organization ID will be used [zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | nl-ams-1) FLAGS: 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 c50ff53ba8..1fdef1d255 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,9 +36,12 @@ ARGS: [placement-group-id] The placement group ID in witch the server has to be created [bootscript-id] The bootscript ID to use, if empty the local boot will be used [cloud-init] The cloud-init script to use - [organization-id] Organization ID to use. If none is passed will use default organization ID from the config + [project-id] Project ID to use. If none is passed the default project ID will be used [zone=fr-par-1] Zone to target. If none is passed will use default zone from the config +DEPRECATED ARGS: + [organization-id] Please use project-id instead + FLAGS: -h, --help help for create -w, --wait wait until the server is ready diff --git a/cmd/scw/testdata/test-all-usage-instance-snapshot-create-usage.golden b/cmd/scw/testdata/test-all-usage-instance-snapshot-create-usage.golden index 8faa3b065f..2477493ffc 100644 --- a/cmd/scw/testdata/test-all-usage-instance-snapshot-create-usage.golden +++ b/cmd/scw/testdata/test-all-usage-instance-snapshot-create-usage.golden @@ -19,7 +19,7 @@ ARGS: [name=] Name of the snapshot volume-id UUID of the volume [project] - [organization-id] Organization ID to use. If none is passed will use default organization ID from the config + [organization-id] Organization ID to use. If none is passed the default organization ID will be used [zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | nl-ams-1) FLAGS: diff --git a/cmd/scw/testdata/test-all-usage-instance-volume-create-usage.golden b/cmd/scw/testdata/test-all-usage-instance-volume-create-usage.golden index 4f4c232ca7..12a68169b9 100644 --- a/cmd/scw/testdata/test-all-usage-instance-volume-create-usage.golden +++ b/cmd/scw/testdata/test-all-usage-instance-volume-create-usage.golden @@ -22,7 +22,7 @@ ARGS: [base-volume] [base-snapshot] [project] - [organization-id] Organization ID to use. If none is passed will use default organization ID from the config + [organization-id] Organization ID to use. If none is passed the default organization ID will be used [zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | nl-ams-1) FLAGS: diff --git a/go.sum b/go.sum index 117cfad687..5fd9c857d5 100644 --- a/go.sum +++ b/go.sum @@ -59,36 +59,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200601141019-df9902c66d0d h1:5643F4Bm9aFFz482Zju0EW/oP+XA7j+pRWWL6kVcjkQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200601141019-df9902c66d0d/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200603093029-edb933d7c8d4 h1:zJe5/C/RVqDc2GYekjEhFI0e6mrBqotu85LyjBHWhhA= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200603093029-edb933d7c8d4/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200608155405-2c0edbf44628 h1:7sBvEdHVZ+IkOAVh6rizkyQOnCz4xQ5V/qMXqtKF36I= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200608155405-2c0edbf44628/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200610085115-f7eab704fcbe h1:sDuQ3A081/brDcQr5AFz8JwX9Od1v8w3sW/L/g25UfY= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200610085115-f7eab704fcbe/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200622145638-4884584cccc2 h1:Pbot5MmWbvw0K2HqVjcJo3+J7l6vGbB852f7CNeKfyQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200622145638-4884584cccc2/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200622150328-6c000c76bc65 h1:QhG9tN5aUb20f6W8ecoH9XZwa4+0sg6ovBZrYKkp7C0= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200622150328-6c000c76bc65/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200624111939-0a4e128e532e h1:Ef+KTOg0kjZe14rHHoC7VOjiZBrxvuViVGaga7/BLM8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200624111939-0a4e128e532e/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200707130522-abc4aeb2a4e6 h1:mCYMQVdy3ciDx7jtDnRuxTk9IUB525PhZYkCTjMWQUI= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200707130522-abc4aeb2a4e6/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200710161155-10382899255f h1:FHSh2peVlKEecLqHX/8uevrWqeua68LbVBOzIhC0HBw= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200710161155-10382899255f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200713125201-82e19f805d12 h1:JqiinsAqmg65byKaTYomB48jf/7bOq9zs+5TprjnKvI= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200713125201-82e19f805d12/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200715091506-c51118fe6906 h1:Nspt1o8HahZ8BwgaxQUUQeqCOr3ojt/5f2gFH8gI/wo= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200715091506-c51118fe6906/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200717161204-1542ce02b602 h1:Yd8SJyi0yLMSXSwjH/iyliBPyySdwt/+n/ea1YoulZ4= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200717161204-1542ce02b602/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200810155502-64702d7341d2 h1:HzsSvXy7L36098vVrNS331FIum7XDKNLvExwkl3yT+I= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200810155502-64702d7341d2/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200818160321-42f4b6772b5c h1:5R916mdrgsgkZZQAeJxuUtciIv0yoX0UUTtBXsfOgJE= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200818160321-42f4b6772b5c/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200907085655-d6f50c14b691 h1:VvsH7nT47XoQwNKJCcNKTKPY8ennOzCtwSSQk1HFHH0= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200907085655-d6f50c14b691/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200828151447-c88def765356 h1:cL3Kx+H/cVgS8Fhkk0nR2GC5v2NyhNrI8VF9coACJ5Y= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200828151447-c88def765356/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= diff --git a/internal/core/arg_specs.go b/internal/core/arg_specs.go index 993c99ee9b..299fbadb3d 100644 --- a/internal/core/arg_specs.go +++ b/internal/core/arg_specs.go @@ -84,6 +84,9 @@ type ArgSpec struct { // Only one argument of the same OneOfGroup could be specified OneOfGroup string + + // Deprecated is used to flag an argument as deprecated. + Deprecated bool } func (a *ArgSpec) Prefix() string { @@ -161,10 +164,27 @@ func OrganizationIDArgSpec() *ArgSpec { } } +func OrganizationIDDeprecatedArgSpec() *ArgSpec { + return &ArgSpec{ + Name: "organization-id", + Short: "Please use project-id instead", + ValidateFunc: ValidateOrganizationID(), + Deprecated: true, + } +} + func OrganizationArgSpec() *ArgSpec { return &ArgSpec{ Name: "organization", - Short: "Organization ID to use. If none is passed will use default organization ID from the config", + Short: "Organization ID to use. If none is passed the default organization ID will be used", ValidateFunc: ValidateOrganizationID(), } } + +func ProjectIDArgSpec() *ArgSpec { + return &ArgSpec{ + Name: "project-id", + Short: "Project ID to use. If none is passed the default project ID will be used", + ValidateFunc: ValidateProjectID(), + } +} diff --git a/internal/core/autocomplete.go b/internal/core/autocomplete.go index c82d04a825..c312501147 100644 --- a/internal/core/autocomplete.go +++ b/internal/core/autocomplete.go @@ -217,6 +217,11 @@ func BuildAutoCompleteTree(commands *Commands) *AutoCompleteNode { // We consider ArgSpecs as leaf in the autocomplete tree. for _, argSpec := range cmd.ArgSpecs { + if argSpec.Deprecated { + // Do not autocomplete deprecated arguments. + continue + } + if argSpec.Positional { node.Children[positionalValueNodeID] = NewAutoCompleteArgNode(cmd, argSpec) continue diff --git a/internal/core/cobra_builder.go b/internal/core/cobra_builder.go index 64241f6470..96bcd77928 100644 --- a/internal/core/cobra_builder.go +++ b/internal/core/cobra_builder.go @@ -97,7 +97,11 @@ func (b *cobraBuilder) hydrateCobra(cobraCmd *cobra.Command, cmd *Command) { } if cmd.ArgsType != nil { - cobraCmd.Annotations["UsageArgs"] = buildUsageArgs(b.ctx, cmd) + cobraCmd.Annotations["UsageArgs"] = buildUsageArgs(b.ctx, cmd, false) + } + + if cmd.ArgSpecs != nil { + cobraCmd.Annotations["UsageDeprecatedArgs"] = buildUsageArgs(b.ctx, cmd, true) } if cmd.Examples != nil { @@ -139,7 +143,10 @@ EXAMPLES: {{.Annotations.Examples}}{{end}}{{if .Annotations.UsageArgs}} ARGS: -{{.Annotations.UsageArgs}}{{end}}{{if .HasAvailableSubCommands}} +{{.Annotations.UsageArgs}}{{end}}{{if .Annotations.UsageDeprecatedArgs}} + +DEPRECATED ARGS: +{{.Annotations.UsageDeprecatedArgs}}{{end}}{{if .HasAvailableSubCommands}} AVAILABLE COMMANDS:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} {{rpad .Name .NamePadding }} {{if .Short}}{{.Short}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} diff --git a/internal/core/cobra_usage_builder.go b/internal/core/cobra_usage_builder.go index 9051d1323d..2a788d41ba 100644 --- a/internal/core/cobra_usage_builder.go +++ b/internal/core/cobra_usage_builder.go @@ -18,12 +18,21 @@ const ( ) // buildUsageArgs builds usage args string. +// If deprecated is true, true only deprecated argSpecs will be considered. // This string will be used by cobra usage template. -func buildUsageArgs(ctx context.Context, cmd *Command) string { +func buildUsageArgs(ctx context.Context, cmd *Command, deprecated bool) string { var argsBuffer bytes.Buffer tw := tabwriter.NewWriter(&argsBuffer, 0, 0, 3, ' ', 0) - err := _buildUsageArgs(ctx, tw, cmd.ArgSpecs) + // Filter deprecated argSpecs. + argSpecs := ArgSpecs(nil) + for _, argSpec := range cmd.ArgSpecs { + if argSpec.Deprecated == deprecated { + argSpecs = append(argSpecs, argSpec) + } + } + + err := _buildUsageArgs(ctx, tw, argSpecs) if err != nil { // TODO: decide how to handle this error err = fmt.Errorf("building %v: %v", cmd.getPath(), err) diff --git a/internal/core/cobra_usage_builder_test.go b/internal/core/cobra_usage_builder_test.go index 9612054eed..18810d988d 100644 --- a/internal/core/cobra_usage_builder_test.go +++ b/internal/core/cobra_usage_builder_test.go @@ -76,7 +76,7 @@ func Test_buildUsageArgs(t *testing.T) { Short: "Additional volume name", }, }, - }) + }, false) assert.Equal(t, want, got) } diff --git a/internal/core/cobra_utils.go b/internal/core/cobra_utils.go index 0a83376056..f6470eaa36 100644 --- a/internal/core/cobra_utils.go +++ b/internal/core/cobra_utils.go @@ -128,7 +128,7 @@ func run(ctx context.Context, cobraCmd *cobra.Command, cmd *Command, rawArgs []s if cmd.ValidateFunc != nil { validateFunc = cmd.ValidateFunc } - err = validateFunc(cmd, cmdArgs, rawArgs) + err = validateFunc(ctx, cmd, cmdArgs, rawArgs) if err != nil { return nil, err } @@ -190,7 +190,9 @@ func handleUnmarshalErrors(cmd *Command, unmarshalErr *args.UnmarshalArgError) e case *args.UnknownArgError, *args.InvalidArgNameError: argNames := []string(nil) for _, argSpec := range cmd.ArgSpecs { - argNames = append(argNames, argSpec.Name) + if !argSpec.Deprecated { + argNames = append(argNames, argSpec.Name) + } } return &CliError{ diff --git a/internal/core/context.go b/internal/core/context.go index e30b2f5825..bc832e96fa 100644 --- a/internal/core/context.go +++ b/internal/core/context.go @@ -62,6 +62,15 @@ func GetOrganizationIDFromContext(ctx context.Context) (organizationID string) { return organizationID } +func GetProjectIDFromContext(ctx context.Context) (projectID string) { + client := ExtractClient(ctx) + projectID, exists := client.GetDefaultProjectID() + if !exists { + panic("no default project ID found") + } + return projectID +} + func ExtractClient(ctx context.Context) *scw.Client { return extractMeta(ctx).Client } diff --git a/internal/core/validate.go b/internal/core/validate.go index a7f5a23c40..b3a626231b 100644 --- a/internal/core/validate.go +++ b/internal/core/validate.go @@ -1,6 +1,7 @@ package core import ( + "context" "fmt" "reflect" "strconv" @@ -14,14 +15,14 @@ import ( // CommandValidateFunc validates en entire command. // Used in core.cobraRun(). -type CommandValidateFunc func(cmd *Command, cmdArgs interface{}, rawArgs args.RawArgs) error +type CommandValidateFunc func(ctx context.Context, cmd *Command, cmdArgs interface{}, rawArgs args.RawArgs) error // ArgSpecValidateFunc validates one argument of a command. type ArgSpecValidateFunc func(argSpec *ArgSpec, value interface{}) error // DefaultCommandValidateFunc is the default validation function for commands. func DefaultCommandValidateFunc() CommandValidateFunc { - return func(cmd *Command, cmdArgs interface{}, rawArgs args.RawArgs) error { + return func(ctx context.Context, cmd *Command, cmdArgs interface{}, rawArgs args.RawArgs) error { err := validateArgValues(cmd, cmdArgs) if err != nil { return err @@ -34,6 +35,8 @@ func DefaultCommandValidateFunc() CommandValidateFunc { if err != nil { return err } + + validateDeprecated(ctx, cmd) return nil } } @@ -109,6 +112,15 @@ func validateNoConflict(cmd *Command, rawArgs args.RawArgs) error { return nil } +func validateDeprecated(ctx context.Context, cmd *Command) { + for _, argSpec := range cmd.ArgSpecs { + if argSpec.Deprecated { + helpCmd := cmd.GetCommandLine(extractMeta(ctx).BinaryName) + " --help" + ExtractLogger(ctx).Warningf("The argument '%s' is deprecated, more info with: %s\n", argSpec.Name, helpCmd) + } + } +} + // DefaultArgSpecValidateFunc validates a value passed for an ArgSpec // Uses ArgSpec.EnumValues func DefaultArgSpecValidateFunc() ArgSpecValidateFunc { @@ -179,3 +191,24 @@ func ValidateOrganizationID() ArgSpecValidateFunc { return nil } } + +// ValidateProjectID validates a non-required project ID. +// By default, for most command, the project ID is not required. +// In that case, we allow the empty-string value "". +func ValidateProjectID() ArgSpecValidateFunc { + return func(argSpec *ArgSpec, valueI interface{}) error { + value, isStr := valueI.(string) + valuePtr, isPtr := valueI.(*string) + if !isStr && isPtr && valuePtr != nil { + value = *valuePtr + } + + if value == "" && !argSpec.Required { + return nil + } + if !validation.IsProjectID(value) { + return InvalidProjectIDError(value) + } + return nil + } +} diff --git a/internal/core/validate_test.go b/internal/core/validate_test.go index 0cd8379dda..87d71ea66a 100644 --- a/internal/core/validate_test.go +++ b/internal/core/validate_test.go @@ -1,6 +1,7 @@ package core import ( + "context" "fmt" "testing" @@ -37,7 +38,7 @@ func Test_DefaultCommandValidateFunc(t *testing.T) { run := func(testCase TestCase) func(t *testing.T) { return func(t *testing.T) { - err := DefaultCommandValidateFunc()(testCase.command, testCase.parsedArguments, testCase.rawArgs) + err := DefaultCommandValidateFunc()(context.Background(), testCase.command, testCase.parsedArguments, testCase.rawArgs) assert.Equal(t, fmt.Errorf("arg validation called"), err) } } @@ -194,14 +195,14 @@ func Test_DefaultCommandRequiredFunc(t *testing.T) { runOK := func(testCase TestCase) func(t *testing.T) { return func(t *testing.T) { - err := DefaultCommandValidateFunc()(testCase.command, testCase.parsedArguments, testCase.rawArgs) + err := DefaultCommandValidateFunc()(context.Background(), testCase.command, testCase.parsedArguments, testCase.rawArgs) assert.Equal(t, nil, err) } } runErr := func(testCase TestCase, argName string) func(t *testing.T) { return func(t *testing.T) { - err := DefaultCommandValidateFunc()(testCase.command, testCase.parsedArguments, testCase.rawArgs) + err := DefaultCommandValidateFunc()(context.Background(), testCase.command, testCase.parsedArguments, testCase.rawArgs) assert.Equal(t, MissingRequiredArgumentError(argName), err) } } @@ -343,3 +344,29 @@ func Test_ValidateNoConflict(t *testing.T) { arg2: "all-ssh-keys", })) } + +func Test_ValidateDeprecated(t *testing.T) { + type TestCase struct { + command *Command + rawArgs args.RawArgs + arg1 string + arg2 string + } + + t.Run("Deprecated", func(t *testing.T) { + testCase := TestCase{ + command: &Command{ + ArgSpecs: ArgSpecs{ + { + Name: "a", + Deprecated: true, + }, + }, + }, + rawArgs: []string{"a=yo"}, + } + err := validateNoConflict(testCase.command, testCase.rawArgs) + assert.Equal(t, nil, err) + }) + +} diff --git a/internal/docgen/docgen.go b/internal/docgen/docgen.go index 2f499b0540..fa7a87748d 100644 --- a/internal/docgen/docgen.go +++ b/internal/docgen/docgen.go @@ -124,6 +124,9 @@ func newTemplate() *template.Template { }, "arg_spec_flag": func(arg *core.ArgSpec) string { parts := []string(nil) + if arg.Deprecated { + parts = append(parts, "Deprecated") + } if arg.Required { parts = append(parts, "Required") } @@ -136,6 +139,13 @@ func newTemplate() *template.Template { } return strings.Join(parts, "
") }, + "arg_spec_name": func(arg *core.ArgSpec) string { + res := arg.Name + if arg.Deprecated { + res = "~~" + arg.Name + "~~" + } + return res + }, "default": func(defaultValue string, value string) string { if value == "" { return defaultValue diff --git a/internal/docgen/tpl.go b/internal/docgen/tpl.go index e47daea8c0..22452635ec 100644 --- a/internal/docgen/tpl.go +++ b/internal/docgen/tpl.go @@ -49,7 +49,7 @@ const tplStr = ` | Name | | Description | |------|---|-------------| {{ range $arg := .Cmd.ArgSpecs -}} -| {{ $arg.Name }} | {{ arg_spec_flag $arg }} | {{ $arg.Short }} | +| {{ arg_spec_name $arg }} | {{ arg_spec_flag $arg }} | {{ $arg.Short }} | {{ end -}} {{- end -}} {{- if .Cmd.Examples }} diff --git a/internal/namespaces/instance/v1/custom_server_create.go b/internal/namespaces/instance/v1/custom_server_create.go index 047ca95812..977e80f53b 100644 --- a/internal/namespaces/instance/v1/custom_server_create.go +++ b/internal/namespaces/instance/v1/custom_server_create.go @@ -21,7 +21,7 @@ import ( // TODO: Add cloud-init type instanceCreateServerRequest struct { Zone scw.Zone - OrganizationID *string + ProjectID *string Image string Type string Name string @@ -35,6 +35,9 @@ type instanceCreateServerRequest struct { PlacementGroupID string BootscriptID string CloudInit string + + // Deprecated, use project-id instead + OrganizationID *string } // TODO: Remove all error uppercase and punctuations when [APIGW-1367] will be done @@ -105,7 +108,8 @@ func serverCreateCommand() *core.Command { Name: "cloud-init", Short: "The cloud-init script to use", }, - core.OrganizationIDArgSpec(), + core.ProjectIDArgSpec(), + core.OrganizationIDDeprecatedArgSpec(), core.ZoneArgSpec(), }, Run: instanceServerCreateRun, @@ -163,6 +167,7 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac serverReq := &instance.CreateServerRequest{ Zone: args.Zone, Organization: args.OrganizationID, + Project: args.ProjectID, Name: args.Name, CommercialType: args.Type, EnableIPv6: args.IPv6, @@ -253,17 +258,24 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac // More format details in buildVolumeTemplate function. // if len(args.AdditionalVolumes) > 0 || args.RootVolume != "" { - // Get default organization ID. - organizationID := args.OrganizationID - if organizationID == nil { - orgFromCtx := core.GetOrganizationIDFromContext(ctx) - if orgFromCtx != "" { - organizationID = &orgFromCtx + // Get project ID. + projectID := args.ProjectID + if projectID == nil { + if projectIDFromCtx := core.GetProjectIDFromContext(ctx); projectIDFromCtx != "" { + projectID = &projectIDFromCtx + } + } + if projectID == nil { + projectID = args.OrganizationID + } + if projectID == nil { + if orgIDFromCtx := core.GetOrganizationIDFromContext(ctx); orgIDFromCtx != "" { + projectID = &orgIDFromCtx } } // Create initial volume template map. - volumes, err := buildVolumes(apiInstance, args.Zone, organizationID, serverReq.Name, args.RootVolume, args.AdditionalVolumes) + volumes, err := buildVolumes(apiInstance, args.Zone, projectID, serverReq.Name, args.RootVolume, args.AdditionalVolumes) if err != nil { return nil, err } @@ -333,13 +345,17 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac // if needIPCreation { logger.Debugf("creating IP") - organizationID := (*string)(nil) - if args.OrganizationID != nil { - organizationID = args.OrganizationID + projectID := (*string)(nil) + if args.ProjectID != nil { + projectID = args.ProjectID } + if projectID == nil { + projectID = args.OrganizationID + } + res, err := apiInstance.CreateIP(&instance.CreateIPRequest{ - Zone: args.Zone, - Organization: organizationID, + Zone: args.Zone, + Project: projectID, }) if err != nil { return nil, fmt.Errorf("error while creating your public IP: %s", err) @@ -410,19 +426,19 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac // buildVolumes creates the initial volume map. // It is not the definitive one, it will be mutated all along the process. -func buildVolumes(api *instance.API, zone scw.Zone, organizationID *string, serverName, rootVolume string, additionalVolumes []string) (map[string]*instance.VolumeTemplate, error) { +func buildVolumes(api *instance.API, zone scw.Zone, projectID *string, serverName, rootVolume string, additionalVolumes []string) (map[string]*instance.VolumeTemplate, error) { volumes := make(map[string]*instance.VolumeTemplate) if rootVolume != "" { - rootVolumeTemplate, err := buildVolumeTemplate(api, zone, organizationID, rootVolume) + rootVolumeTemplate, err := buildVolumeTemplate(api, zone, projectID, rootVolume) if err != nil { return nil, err } - rootVolumeTemplate.Organization = "" + rootVolumeTemplate.Project = "" volumes["0"] = rootVolumeTemplate } for i, v := range additionalVolumes { - volumeTemplate, err := buildVolumeTemplate(api, zone, organizationID, v) + volumeTemplate, err := buildVolumeTemplate(api, zone, projectID, v) if err != nil { return nil, err } @@ -448,7 +464,7 @@ func buildVolumes(api *instance.API, zone scw.Zone, organizationID *string, serv // - a "creation" format: ^((local|l|block|b):)?\d+GB?$ (size is handled by go-humanize, so other sizes are supported) // - a UUID format // -func buildVolumeTemplate(api *instance.API, zone scw.Zone, orgID *string, flagV string) (*instance.VolumeTemplate, error) { +func buildVolumeTemplate(api *instance.API, zone scw.Zone, projID *string, flagV string) (*instance.VolumeTemplate, error) { parts := strings.Split(strings.TrimSpace(flagV), ":") // Create volume. @@ -470,8 +486,8 @@ func buildVolumeTemplate(api *instance.API, zone scw.Zone, orgID *string, flagV } vt.Size = scw.Size(size) - if orgID != nil { - vt.Organization = *orgID + if projID != nil { + vt.Project = *projID } return vt, nil