Skip to content

Commit 73e8cf4

Browse files
authored
Automatically fetch stack version in ecctl deployment create --version isn't provided (#761)
When `--version` is not provided to `ecctl deployment create`, the command now automatically fetches the latest available stack version from the API and uses it. This avoids a confusing error when users omit the `--version` flag. Key changes in `create.go`: - Added a call to `getLatestStackVersion()` that queries `stackapi.List` for available stack versions and picks the first (latest) one. - The auto-fetch is skipped when `--file` is provided, since file-based payloads carry their own version. - Prints a message to stderr (`--version not set, using latest: X.Y.Z`) so the user knows which version was selected.
1 parent 93e4864 commit 73e8cf4

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

cmd/deployment/create.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/blang/semver/v4"
2626
"github.com/elastic/cloud-sdk-go/pkg/api/deploymentapi"
2727
"github.com/elastic/cloud-sdk-go/pkg/api/deploymentapi/deptemplateapi"
28+
"github.com/elastic/cloud-sdk-go/pkg/api/stackapi"
2829
"github.com/elastic/cloud-sdk-go/pkg/models"
2930
"github.com/elastic/cloud-sdk-go/pkg/multierror"
3031
sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
@@ -45,8 +46,18 @@ var createCmd = &cobra.Command{
4546
generatePayload, _ := cmd.Flags().GetBool("generate-payload")
4647
name, _ := cmd.Flags().GetString("name")
4748
version, _ := cmd.Flags().GetString("version")
49+
file, _ := cmd.Flags().GetString("file")
4850
region := ecctl.Get().Config.Region
4951

52+
if version == "" && file == "" {
53+
var err error
54+
version, err = getLatestStackVersion(region)
55+
if err != nil {
56+
return err
57+
}
58+
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "--version not set, using latest: %s\n", version)
59+
}
60+
5061
payload, err := newCreatePayload(cmd, version, region)
5162
if err != nil {
5263
return err
@@ -111,6 +122,21 @@ func initFlags() {
111122
createCmd.Flags().Bool("minimum-size", false, "Shrink each Elasticsearch topology element to its minimum allowed size")
112123
}
113124

125+
func getLatestStackVersion(region string) (string, error) {
126+
stacks, err := stackapi.List(stackapi.ListParams{
127+
API: ecctl.Get().API,
128+
Region: region,
129+
})
130+
if err != nil {
131+
return "", fmt.Errorf("unable to fetch stack versions: %w", err)
132+
}
133+
if len(stacks.Stacks) == 0 {
134+
return "", errors.New("no stack versions available")
135+
}
136+
// List returns stacks sorted descending, so first is latest.
137+
return stacks.Stacks[0].Version, nil
138+
}
139+
114140
func getDefaultTemplate(region, stackVersion string) (string, error) {
115141
showHidden := false
116142
templates, err := deptemplateapi.List(deptemplateapi.ListParams{

cmd/deployment/create_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,79 @@ Deployment [%s] - [Apm][%s]: running step "waiting-for-some-step" (Plan duration
272272
).Error(),
273273
},
274274
},
275+
{
276+
name: "succeeds creating a deployment without --version by fetching latest stack version",
277+
args: testutils.Args{
278+
Cmd: createCmd,
279+
Args: []string{
280+
"create",
281+
"--request-id=some_request_id",
282+
"--deployment-template=aws-io-optimized-v2",
283+
"--name=with_default",
284+
},
285+
Cfg: testutils.MockCfg{Responses: []mock.Response{
286+
{
287+
Response: http.Response{
288+
StatusCode: 200,
289+
Body: mock.NewStructBody(models.StackVersionConfigs{
290+
Stacks: []*models.StackVersionConfig{
291+
{Version: "7.8.0"},
292+
{Version: "7.7.0"},
293+
{Version: "7.6.0"},
294+
},
295+
}),
296+
},
297+
Assert: &mock.RequestAssertion{
298+
Method: "GET",
299+
Header: api.DefaultReadMockHeaders,
300+
Path: "/api/v1/regions/ece-region/stack/versions",
301+
Host: api.DefaultMockHost,
302+
Query: url.Values{
303+
"show_deleted": {"false"},
304+
},
305+
},
306+
},
307+
{
308+
Response: http.Response{
309+
StatusCode: 200,
310+
Body: mock.NewByteBody(awsIoOptimisedTemplate),
311+
},
312+
Assert: &mock.RequestAssertion{
313+
Method: "GET",
314+
Header: api.DefaultReadMockHeaders,
315+
Path: "/api/v1/deployments/templates/aws-io-optimized-v2",
316+
Host: api.DefaultMockHost,
317+
Query: url.Values{
318+
"region": {"ece-region"},
319+
"show_instance_configurations": {"true"},
320+
"show_max_zones": {"false"},
321+
"stack_version": {"7.8.0"},
322+
},
323+
},
324+
},
325+
{
326+
Response: http.Response{
327+
StatusCode: 201,
328+
Body: mock.NewStructBody(defaultCreateResponse),
329+
},
330+
Assert: &mock.RequestAssertion{
331+
Method: "POST",
332+
Header: api.DefaultWriteMockHeaders,
333+
Body: mock.NewStringBody(`{"name":"with_default","resources":{"apm":[{"elasticsearch_cluster_ref_id":"main-elasticsearch","plan":{"apm":{"version":"7.8.0"},"cluster_topology":[{"instance_configuration_id":"aws.apm.r5d","size":{"resource":"memory","value":512},"zone_count":1}]},"ref_id":"main-apm","region":"us-east-1"}],"appsearch":null,"elasticsearch":[{"plan":{"autoscaling_enabled":false,"cluster_topology":[{"id":"coordinating","instance_configuration_id":"aws.coordinating.m5d","node_roles":null,"node_type":{"data":false,"ingest":true,"master":false},"size":{"resource":"memory","value":0},"topology_element_control":{"min":{"resource":"memory","value":0}},"zone_count":2},{"autoscaling_max":{"resource":"memory","value":118784},"elasticsearch":{"node_attributes":{"data":"hot"}},"id":"hot_content","instance_configuration_id":"aws.data.highio.i3","node_roles":null,"node_type":{"data":true,"ingest":true,"master":true},"size":{"resource":"memory","value":8192},"topology_element_control":{"min":{"resource":"memory","value":1024}},"zone_count":2},{"autoscaling_max":{"resource":"memory","value":118784},"elasticsearch":{"node_attributes":{"data":"warm"}},"id":"warm","instance_configuration_id":"aws.data.highstorage.d3","node_roles":null,"node_type":{"data":true,"ingest":false,"master":false},"size":{"resource":"memory","value":0},"topology_element_control":{"min":{"resource":"memory","value":0}},"zone_count":2},{"autoscaling_max":{"resource":"memory","value":59392},"elasticsearch":{"node_attributes":{"data":"cold"}},"id":"cold","instance_configuration_id":"aws.data.highstorage.d3","node_roles":null,"node_type":{"data":true,"ingest":false,"master":false},"size":{"resource":"memory","value":0},"topology_element_control":{"min":{"resource":"memory","value":0}},"zone_count":1},{"id":"master","instance_configuration_id":"aws.master.r5d","node_roles":null,"node_type":{"data":false,"ingest":false,"master":true},"size":{"resource":"memory","value":0},"topology_element_control":{"min":{"resource":"memory","value":0}},"zone_count":3},{"autoscaling_max":{"resource":"memory","value":61440},"autoscaling_min":{"resource":"memory","value":0},"id":"ml","instance_configuration_id":"aws.ml.m5d","node_roles":null,"node_type":{"data":false,"ingest":false,"master":false,"ml":true},"size":{"resource":"memory","value":0},"topology_element_control":{"min":{"resource":"memory","value":0}},"zone_count":1}],"deployment_template":{"id":"aws-io-optimized-v2"},"elasticsearch":{"version":"7.8.0"}},"ref_id":"main-elasticsearch","region":"us-east-1","settings":{"dedicated_masters_threshold":6}}],"enterprise_search":[{"elasticsearch_cluster_ref_id":"main-elasticsearch","plan":{"cluster_topology":[{"instance_configuration_id":"aws.enterprisesearch.m5d","node_type":{"appserver":true,"connector":true,"worker":true},"size":{"resource":"memory","value":0},"zone_count":2}],"enterprise_search":{"version":"7.8.0"}},"ref_id":"main-enterprise_search","region":"us-east-1"}],"integrations_server":null,"kibana":[{"elasticsearch_cluster_ref_id":"main-elasticsearch","plan":{"cluster_topology":[{"instance_configuration_id":"aws.kibana.r5d","size":{"resource":"memory","value":1024},"zone_count":1}],"kibana":{"version":"7.8.0"}},"ref_id":"main-kibana","region":"us-east-1"}]}}` + "\n"),
334+
Path: "/api/v1/deployments",
335+
Host: api.DefaultMockHost,
336+
Query: url.Values{
337+
"request_id": {"some_request_id"},
338+
},
339+
},
340+
},
341+
}},
342+
},
343+
want: testutils.Assertion{
344+
Stdout: string(defaultCreateResponseBytes),
345+
Stderr: "--version not set, using latest: 7.8.0\n",
346+
},
347+
},
275348
{
276349
name: "succeeds creating a deployment without --deployment-template by auto-selecting default",
277350
args: testutils.Args{

0 commit comments

Comments
 (0)