diff --git a/cmd/server/create.go b/cmd/server/create.go index 54f32d1..12d2a9d 100644 --- a/cmd/server/create.go +++ b/cmd/server/create.go @@ -104,6 +104,18 @@ var createCmd = &cobra.Command{ if err != nil { return fmt.Errorf("flavor %q not found: %w", flavorID, err) } + // Refuse built-in-disk (g2d-*) flavors up front (#196). They require + // a server-side block_device_mapping payload the CLI does not + // currently build; the upstream API would otherwise return a + // confusing HTTP 400 ("block_device_mapping required") well after + // the create-summary prompt. + if isUnsupportedFlavor(flavor.Name) { + return fmt.Errorf( + "flavor %q is not supported by this CLI (built-in-disk family).\n"+ + "Use a volume-backed flavor instead: g2l-* (Linux) or g2w-* (Windows).\n"+ + "List available flavors with: conoha flavor list", + flavor.Name) + } if !isUsableFlavor(flavor.Name) { fmt.Fprintf(os.Stderr, "Warning: flavor %q may not be supported via public API\n", flavor.Name) } @@ -329,6 +341,14 @@ func isUsableFlavor(name string) bool { return strings.HasPrefix(name, "g2l-t-") } +// isUnsupportedFlavor reports flavors that the CLI knows it cannot create +// successfully today. g2d-* are built-in-disk flavors that require a +// server-side block_device_mapping payload this CLI does not currently +// build (see #196); refusing them early avoids a misleading HTTP 400. +func isUnsupportedFlavor(name string) bool { + return strings.HasPrefix(name, "g2d-") +} + func selectFlavor(compute *api.ComputeAPI) (*model.Flavor, error) { flavors, err := compute.ListFlavors() if err != nil { diff --git a/cmd/server/create_test.go b/cmd/server/create_test.go index 914fff2..2e7fb0c 100644 --- a/cmd/server/create_test.go +++ b/cmd/server/create_test.go @@ -177,6 +177,28 @@ func TestIsUsableFlavor(t *testing.T) { } } +// #196: g2d-* (built-in-disk family) must be rejected early. +func TestIsUnsupportedFlavor(t *testing.T) { + tests := []struct { + name string + want bool + }{ + {"g2d-t-c2m4d60", true}, + {"g2d-t-c4m8d100", true}, + {"g2l-t-c2m4", false}, + {"g2w-t-c2m4", false}, + {"g2lp-t-c2m4", false}, // not a 'g2d-' prefix even if 'd' appears later + {"", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isUnsupportedFlavor(tt.name); got != tt.want { + t.Errorf("isUnsupportedFlavor(%q) = %v, want %v", tt.name, got, tt.want) + } + }) + } +} + func TestResolveUserData_MutualExclusion(t *testing.T) { cmd := newTestCmd(map[string]string{ "user-data-raw": "echo hi",