diff --git a/cli/cmd/definition.go b/cli/cmd/definition.go index 16b17c7..a10a153 100644 --- a/cli/cmd/definition.go +++ b/cli/cmd/definition.go @@ -81,7 +81,7 @@ Examples: case output.FormatYAML: return printer.PrintYAML(resp) default: - headers := []string{"ID", "NAME", "DESCRIPTION", "OWNER", "CHARTS"} + headers := []string{"ID", "NAME", "DESCRIPTION", "OWNER"} rows := make([][]string, len(resp.Data)) for i, d := range resp.Data { rows[i] = []string{ @@ -89,7 +89,6 @@ Examples: d.Name, d.Description, d.Owner, - strconv.Itoa(len(d.Charts)), } } return printer.PrintTable(headers, rows) @@ -391,7 +390,7 @@ func printDefinition(def *types.StackDefinition) error { for _, ch := range def.Charts { fields = append(fields, output.KeyValue{ Key: "Chart", - Value: fmt.Sprintf("%s (%s@%s)", ch.Name, ch.RepoURL, ch.ChartVersion), + Value: fmt.Sprintf("%s (%s@%s)", ch.ChartName, ch.RepoURL, ch.ChartVersion), }) } return printer.PrintSingle(def, fields) diff --git a/cli/cmd/definition_test.go b/cli/cmd/definition_test.go index 1f9afe5..7e3e93a 100644 --- a/cli/cmd/definition_test.go +++ b/cli/cmd/definition_test.go @@ -66,12 +66,10 @@ func TestDefinitionListCmd_TableOutput(t *testing.T) { assert.Contains(t, out, "NAME") assert.Contains(t, out, "DESCRIPTION") assert.Contains(t, out, "OWNER") - assert.Contains(t, out, "CHARTS") assert.Contains(t, out, "5") assert.Contains(t, out, "api-service") assert.Contains(t, out, "API microservice stack") assert.Contains(t, out, "admin") - assert.Contains(t, out, "1") } func TestDefinitionListCmd_JSONOutput(t *testing.T) { diff --git a/cli/cmd/stack.go b/cli/cmd/stack.go index 57dab9b..ca7091c 100644 --- a/cli/cmd/stack.go +++ b/cli/cmd/stack.go @@ -203,17 +203,17 @@ Examples: return err } - log, err := c.DeployStack(id) + resp, err := c.DeployStack(id) if err != nil { return err } if printer.Quiet { - fmt.Fprintln(printer.Writer, log.ID) + fmt.Fprintln(printer.Writer, resp.LogID) return nil } - printer.PrintMessage("Deploying stack %s... (log ID: %s)", id, log.ID) + printer.PrintMessage("Deploying stack %s... (log ID: %s)", id, resp.LogID) return nil }, } @@ -238,17 +238,17 @@ Examples: return err } - log, err := c.StopStack(id) + resp, err := c.StopStack(id) if err != nil { return err } if printer.Quiet { - fmt.Fprintln(printer.Writer, log.ID) + fmt.Fprintln(printer.Writer, resp.LogID) return nil } - printer.PrintMessage("Stopping stack %s... (log ID: %s)", id, log.ID) + printer.PrintMessage("Stopping stack %s... (log ID: %s)", id, resp.LogID) return nil }, } @@ -286,17 +286,17 @@ Examples: return err } - log, err := c.CleanStack(id) + resp, err := c.CleanStack(id) if err != nil { return err } if printer.Quiet { - fmt.Fprintln(printer.Writer, log.ID) + fmt.Fprintln(printer.Writer, resp.LogID) return nil } - printer.PrintMessage("Cleaning stack %s... (log ID: %s)", id, log.ID) + printer.PrintMessage("Cleaning stack %s... (log ID: %s)", id, resp.LogID) return nil }, } diff --git a/cli/cmd/stack_test.go b/cli/cmd/stack_test.go index 9124209..1ff9592 100644 --- a/cli/cmd/stack_test.go +++ b/cli/cmd/stack_test.go @@ -364,8 +364,8 @@ func TestStackDeployCmd_Success(t *testing.T) { require.Equal(t, "/api/v1/stack-instances/42/deploy", r.URL.Path) require.Equal(t, http.MethodPost, r.Method) w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ID: "100", InstanceID: "42", Action: "deploy", Status: "started"}) + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{LogID: "100", Message: "Deployment started"}) })) defer server.Close() @@ -381,8 +381,8 @@ func TestStackDeployCmd_Success(t *testing.T) { func TestStackDeployCmd_QuietOutput(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ID: "100"}) + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{LogID: "100", Message: "Deployment started"}) })) defer server.Close() @@ -400,8 +400,8 @@ func TestStackStopCmd_Success(t *testing.T) { require.Equal(t, "/api/v1/stack-instances/42/stop", r.URL.Path) require.Equal(t, http.MethodPost, r.Method) w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ID: "101", InstanceID: "42", Action: "stop", Status: "started"}) + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{LogID: "101", Message: "Stop started"}) })) defer server.Close() @@ -423,7 +423,7 @@ func TestStackCleanCmd_WithConfirmation(t *testing.T) { require.Equal(t, "/api/v1/stack-instances/42/clean", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ID: "102"}) + json.NewEncoder(w).Encode(types.DeployResponse{LogID: "102", Message: "Clean started"}) })) defer server.Close() @@ -474,7 +474,7 @@ func TestStackCleanCmd_WithYesFlag(t *testing.T) { called = true w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ID: "103"}) + json.NewEncoder(w).Encode(types.DeployResponse{LogID: "103", Message: "Clean started"}) })) defer server.Close() @@ -893,8 +893,8 @@ func TestStackDeleteCmd_QuietOutput(t *testing.T) { func TestStackStopCmd_QuietOutput(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ID: "101"}) + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{LogID: "101", Message: "Stop started"}) })) defer server.Close() @@ -927,8 +927,8 @@ func TestStackExtendCmd_QuietOutput(t *testing.T) { func TestStackCleanCmd_QuietOutput(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ID: "102"}) + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{LogID: "102", Message: "Clean started"}) })) defer server.Close() @@ -982,7 +982,7 @@ func TestStackGetCmd_YAMLOutput(t *testing.T) { out := buf.String() assert.Contains(t, out, "name: my-stack") - assert.Contains(t, out, "owner: admin") + assert.Contains(t, out, "owner_id: admin") } func TestStackStatusCmd_YAMLOutput(t *testing.T) { diff --git a/cli/cmd/template.go b/cli/cmd/template.go index 5e43abd..2d7c452 100644 --- a/cli/cmd/template.go +++ b/cli/cmd/template.go @@ -70,7 +70,7 @@ Examples: case output.FormatYAML: return printer.PrintYAML(resp) default: - headers := []string{"ID", "NAME", "DESCRIPTION", "PUBLISHED", "CHARTS"} + headers := []string{"ID", "NAME", "DESCRIPTION", "PUBLISHED", "DEFINITIONS"} rows := make([][]string, len(resp.Data)) for i, t := range resp.Data { published := "false" @@ -82,7 +82,7 @@ Examples: t.Name, t.Description, published, - strconv.Itoa(len(t.Charts)), + strconv.Itoa(t.DefinitionCount), } } return printer.PrintTable(headers, rows) @@ -141,7 +141,7 @@ Examples: for _, ch := range tmpl.Charts { fields = append(fields, output.KeyValue{ Key: "Chart", - Value: fmt.Sprintf("%s (%s@%s)", ch.Name, ch.RepoURL, ch.ChartVersion), + Value: fmt.Sprintf("%s (%s@%s)", ch.ChartName, ch.RepoURL, ch.ChartVersion), }) } return printer.PrintSingle(tmpl, fields) @@ -151,8 +151,8 @@ Examples: var templateInstantiateCmd = &cobra.Command{ Use: "instantiate ", - Short: "Create a stack instance from a template", - Long: `Create a new stack instance from a template. + Short: "Create a stack definition from a template", + Long: `Create a new stack definition from a template. Examples: stackctl template instantiate 1 --name my-stack @@ -180,12 +180,12 @@ Examples: return err } - instance, err := c.InstantiateTemplate(id, req) + def, err := c.InstantiateTemplate(id, req) if err != nil { return err } - return printInstance(instance) + return printDefinition(def) }, } @@ -236,7 +236,7 @@ func init() { templateListCmd.Flags().Int(flagPageSize, 0, "Page size") // template instantiate flags - templateInstantiateCmd.Flags().String("name", "", "Stack instance name (required)") + templateInstantiateCmd.Flags().String("name", "", "Stack definition name (required)") templateInstantiateCmd.Flags().String("branch", "", "Git branch") templateInstantiateCmd.Flags().String("cluster", "", "Target cluster ID") _ = templateInstantiateCmd.MarkFlagRequired("name") diff --git a/cli/cmd/template_test.go b/cli/cmd/template_test.go index 853d133..1b1ea65 100644 --- a/cli/cmd/template_test.go +++ b/cli/cmd/template_test.go @@ -22,11 +22,12 @@ import ( func sampleTemplate() types.StackTemplate { now := time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC) return types.StackTemplate{ - Base: types.Base{ID: "10", CreatedAt: now, UpdatedAt: now, Version: "1"}, - Name: "web-app-template", - Description: "Full web app stack", - Published: true, - Owner: "admin", + Base: types.Base{ID: "10", CreatedAt: now, UpdatedAt: now, Version: "1"}, + Name: "web-app-template", + Description: "Full web app stack", + Published: true, + Owner: "admin", + DefinitionCount: 2, Charts: []types.ChartConfig{ { Base: types.Base{ID: "1"}, @@ -70,7 +71,7 @@ func TestTemplateListCmd_TableOutput(t *testing.T) { assert.Contains(t, out, "NAME") assert.Contains(t, out, "DESCRIPTION") assert.Contains(t, out, "PUBLISHED") - assert.Contains(t, out, "CHARTS") + assert.Contains(t, out, "DEFINITIONS") assert.Contains(t, out, "10") assert.Contains(t, out, "web-app-template") assert.Contains(t, out, "Full web app stack") @@ -217,8 +218,8 @@ func TestTemplateGetCmd_TableOutput(t *testing.T) { assert.Contains(t, out, "Full web app stack") assert.Contains(t, out, "true") assert.Contains(t, out, "admin") - assert.Contains(t, out, "frontend") - assert.Contains(t, out, "backend") + assert.Contains(t, out, "react-app") + assert.Contains(t, out, "api-server") } func TestTemplateGetCmd_JSONOutput(t *testing.T) { @@ -275,7 +276,11 @@ func TestTemplateGetCmd_NotFound(t *testing.T) { // ---------- template instantiate ---------- func TestTemplateInstantiateCmd_Success(t *testing.T) { - instance := sampleStack() + def := types.StackDefinition{ + Base: types.Base{ID: "42"}, + Name: "my-def", + Owner: "uid-1", + } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, "/api/v1/templates/10/instantiate", r.URL.Path) require.Equal(t, http.MethodPost, r.Method) @@ -286,7 +291,7 @@ func TestTemplateInstantiateCmd_Success(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(instance) + json.NewEncoder(w).Encode(def) })) defer server.Close() @@ -304,7 +309,7 @@ func TestTemplateInstantiateCmd_Success(t *testing.T) { out := buf.String() assert.Contains(t, out, "42") - assert.Contains(t, out, "my-stack") + assert.Contains(t, out, "my-def") } func TestTemplateInstantiateCmd_WithBranchAndCluster(t *testing.T) { @@ -317,7 +322,7 @@ func TestTemplateInstantiateCmd_WithBranchAndCluster(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(types.StackInstance{Base: types.Base{ID: "50"}, Name: "my-instance"}) + json.NewEncoder(w).Encode(types.StackDefinition{Base: types.Base{ID: "50"}, Name: "my-instance"}) })) defer server.Close() @@ -371,7 +376,7 @@ func TestTemplateInstantiateCmd_QuietOutput(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(types.StackInstance{Base: types.Base{ID: "50"}}) + json.NewEncoder(w).Encode(types.StackDefinition{Base: types.Base{ID: "50"}}) })) defer server.Close() diff --git a/cli/pkg/client/client.go b/cli/pkg/client/client.go index b6070c5..fea5b8f 100644 --- a/cli/pkg/client/client.go +++ b/cli/pkg/client/client.go @@ -301,33 +301,33 @@ func (c *Client) DeleteStack(id string) error { } // DeployStack triggers a deployment for a stack instance. -func (c *Client) DeployStack(id string) (*types.DeploymentLog, error) { - var log types.DeploymentLog - err := c.Post(fmt.Sprintf("/api/v1/stack-instances/%s/deploy", id), nil, &log) +func (c *Client) DeployStack(id string) (*types.DeployResponse, error) { + var resp types.DeployResponse + err := c.Post(fmt.Sprintf("/api/v1/stack-instances/%s/deploy", id), nil, &resp) if err != nil { return nil, err } - return &log, nil + return &resp, nil } // StopStack triggers a stop for a stack instance. -func (c *Client) StopStack(id string) (*types.DeploymentLog, error) { - var log types.DeploymentLog - err := c.Post(fmt.Sprintf("/api/v1/stack-instances/%s/stop", id), nil, &log) +func (c *Client) StopStack(id string) (*types.DeployResponse, error) { + var resp types.DeployResponse + err := c.Post(fmt.Sprintf("/api/v1/stack-instances/%s/stop", id), nil, &resp) if err != nil { return nil, err } - return &log, nil + return &resp, nil } // CleanStack triggers an undeploy and namespace removal for a stack instance. -func (c *Client) CleanStack(id string) (*types.DeploymentLog, error) { - var log types.DeploymentLog - err := c.Post(fmt.Sprintf("/api/v1/stack-instances/%s/clean", id), nil, &log) +func (c *Client) CleanStack(id string) (*types.DeployResponse, error) { + var resp types.DeployResponse + err := c.Post(fmt.Sprintf("/api/v1/stack-instances/%s/clean", id), nil, &resp) if err != nil { return nil, err } - return &log, nil + return &resp, nil } // GetStackStatus returns the current status and pod states for a stack instance. @@ -424,14 +424,14 @@ func (c *Client) GetTemplate(id string) (*types.StackTemplate, error) { return &tmpl, nil } -// InstantiateTemplate creates a new stack instance from a template. -func (c *Client) InstantiateTemplate(id string, req *types.InstantiateTemplateRequest) (*types.StackInstance, error) { - var instance types.StackInstance - err := c.Post(fmt.Sprintf("/api/v1/templates/%s/instantiate", id), req, &instance) +// InstantiateTemplate creates a new stack definition from a template. +func (c *Client) InstantiateTemplate(id string, req *types.InstantiateTemplateRequest) (*types.StackDefinition, error) { + var def types.StackDefinition + err := c.Post(fmt.Sprintf("/api/v1/templates/%s/instantiate", id), req, &def) if err != nil { return nil, err } - return &instance, nil + return &def, nil } // QuickDeployTemplate creates and deploys a stack instance from a template in one step. diff --git a/cli/pkg/client/client_test.go b/cli/pkg/client/client_test.go index 72119e1..81f1b09 100644 --- a/cli/pkg/client/client_test.go +++ b/cli/pkg/client/client_test.go @@ -707,22 +707,19 @@ func TestDeployStack_Success(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "/api/v1/stack-instances/42/deploy", r.URL.Path) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ - ID: "100", - InstanceID: "42", - Action: "deploy", - Status: "started", + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{ + LogID: "100", + Message: "Deployment started", }) })) defer server.Close() c := New(server.URL) - log, err := c.DeployStack("42") + resp, err := c.DeployStack("42") require.NoError(t, err) - assert.Equal(t, "100", log.ID) - assert.Equal(t, "42", log.InstanceID) - assert.Equal(t, "deploy", log.Action) + assert.Equal(t, "100", resp.LogID) + assert.Equal(t, "Deployment started", resp.Message) } func TestStopStack_Success(t *testing.T) { @@ -730,21 +727,18 @@ func TestStopStack_Success(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "/api/v1/stack-instances/42/stop", r.URL.Path) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ - ID: "101", - InstanceID: "42", - Action: "stop", - Status: "started", + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{ + LogID: "101", + Message: "Stop started", }) })) defer server.Close() c := New(server.URL) - log, err := c.StopStack("42") + resp, err := c.StopStack("42") require.NoError(t, err) - assert.Equal(t, "101", log.ID) - assert.Equal(t, "stop", log.Action) + assert.Equal(t, "101", resp.LogID) } func TestCleanStack_Success(t *testing.T) { @@ -752,21 +746,18 @@ func TestCleanStack_Success(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "/api/v1/stack-instances/42/clean", r.URL.Path) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ - ID: "102", - InstanceID: "42", - Action: "clean", - Status: "started", + w.WriteHeader(http.StatusAccepted) + json.NewEncoder(w).Encode(types.DeployResponse{ + LogID: "102", + Message: "Clean started", }) })) defer server.Close() c := New(server.URL) - log, err := c.CleanStack("42") + resp, err := c.CleanStack("42") require.NoError(t, err) - assert.Equal(t, "102", log.ID) - assert.Equal(t, "clean", log.Action) + assert.Equal(t, "102", resp.LogID) } func TestGetStackStatus_Success(t *testing.T) { @@ -976,24 +967,23 @@ func TestInstantiateTemplate_Success(t *testing.T) { assert.Equal(t, "2", body.ClusterID) w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(types.StackInstance{ - Base: types.Base{ID: "50"}, - Name: "my-instance", - Status: "draft", + json.NewEncoder(w).Encode(types.StackDefinition{ + Base: types.Base{ID: "50"}, + Name: "my-instance", + Owner: "uid-1", }) })) defer server.Close() c := New(server.URL) - instance, err := c.InstantiateTemplate("10", &types.InstantiateTemplateRequest{ + def, err := c.InstantiateTemplate("10", &types.InstantiateTemplateRequest{ Name: "my-instance", Branch: "feature/xyz", ClusterID: "2", }) require.NoError(t, err) - assert.Equal(t, "50", instance.ID) - assert.Equal(t, "my-instance", instance.Name) - assert.Equal(t, "draft", instance.Status) + assert.Equal(t, "50", def.ID) + assert.Equal(t, "my-instance", def.Name) } func TestQuickDeployTemplate_Success(t *testing.T) { diff --git a/cli/pkg/output/output_test.go b/cli/pkg/output/output_test.go index a0290fc..5ca39f8 100644 --- a/cli/pkg/output/output_test.go +++ b/cli/pkg/output/output_test.go @@ -320,14 +320,14 @@ func TestStackInstance_JSON(t *testing.T) { assert.Equal(t, "my-app-feature", result["name"]) assert.Equal(t, "7", result["stack_definition_id"]) assert.Equal(t, "my-app", result["definition_name"]) - assert.Equal(t, "alice", result["owner"]) + assert.Equal(t, "alice", result["owner_id"]) assert.Equal(t, "feature/login", result["branch"]) assert.Equal(t, "my-app-feature-ns", result["namespace"]) assert.Equal(t, "running", result["status"]) assert.Equal(t, "3", result["cluster_id"]) assert.Equal(t, "dev-cluster", result["cluster_name"]) assert.Equal(t, float64(120), result["ttl_minutes"]) - assert.NotEmpty(t, result["deployed_at"]) + assert.NotEmpty(t, result["last_deployed_at"]) assert.NotEmpty(t, result["created_at"]) assert.NotEmpty(t, result["updated_at"]) // deleted_at should be omitted when nil @@ -462,7 +462,7 @@ func TestListResponse_StackInstance_JSON(t *testing.T) { assert.Equal(t, float64(25), result["total"]) assert.Equal(t, float64(2), result["page"]) - assert.Equal(t, float64(10), result["page_size"]) + assert.Equal(t, float64(10), result["pageSize"]) assert.Equal(t, float64(3), result["total_pages"]) dataArr, ok := result["data"].([]interface{}) @@ -991,7 +991,7 @@ func TestListResponse_MultiplePages_JSON(t *testing.T) { assert.Equal(t, float64(50), result["total"]) assert.Equal(t, float64(3), result["page"]) - assert.Equal(t, float64(2), result["page_size"]) + assert.Equal(t, float64(2), result["pageSize"]) assert.Equal(t, float64(25), result["total_pages"]) dataArr, ok := result["data"].([]interface{}) diff --git a/cli/pkg/types/types.go b/cli/pkg/types/types.go index 8aadabe..ac0b252 100644 --- a/cli/pkg/types/types.go +++ b/cli/pkg/types/types.go @@ -17,7 +17,7 @@ type StackInstance struct { Name string `json:"name" yaml:"name"` StackDefinitionID string `json:"stack_definition_id" yaml:"stack_definition_id"` DefinitionName string `json:"definition_name,omitempty" yaml:"definition_name,omitempty"` - Owner string `json:"owner" yaml:"owner"` + Owner string `json:"owner_id" yaml:"owner_id"` Branch string `json:"branch" yaml:"branch"` Namespace string `json:"namespace" yaml:"namespace"` Status string `json:"status" yaml:"status"` @@ -25,7 +25,7 @@ type StackInstance struct { ClusterName string `json:"cluster_name,omitempty" yaml:"cluster_name,omitempty"` TTLMinutes int `json:"ttl_minutes,omitempty" yaml:"ttl_minutes,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty" yaml:"expires_at,omitempty"` - DeployedAt *time.Time `json:"deployed_at,omitempty" yaml:"deployed_at,omitempty"` + DeployedAt *time.Time `json:"last_deployed_at,omitempty" yaml:"last_deployed_at,omitempty"` } // StackDefinition represents a stack definition with its chart configurations. @@ -34,7 +34,7 @@ type StackDefinition struct { Name string `json:"name" yaml:"name"` Description string `json:"description,omitempty" yaml:"description,omitempty"` DefaultBranch string `json:"default_branch" yaml:"default_branch"` - Owner string `json:"owner" yaml:"owner"` + Owner string `json:"owner_id" yaml:"owner_id"` Charts []ChartConfig `json:"charts,omitempty" yaml:"charts,omitempty"` } @@ -43,16 +43,17 @@ type StackTemplate struct { Base Name string `json:"name" yaml:"name"` Description string `json:"description,omitempty" yaml:"description,omitempty"` - Published bool `json:"published" yaml:"published"` - Owner string `json:"owner" yaml:"owner"` - Charts []ChartConfig `json:"charts,omitempty" yaml:"charts,omitempty"` + Published bool `json:"is_published" yaml:"is_published"` + Owner string `json:"owner_id" yaml:"owner_id"` + Charts []ChartConfig `json:"charts,omitempty" yaml:"charts,omitempty"` + DefinitionCount int `json:"definition_count,omitempty" yaml:"definition_count,omitempty"` } // ChartConfig represents a Helm chart configuration within a definition or template. type ChartConfig struct { Base Name string `json:"name" yaml:"name"` - RepoURL string `json:"repo_url" yaml:"repo_url"` + RepoURL string `json:"repository_url" yaml:"repository_url"` ChartName string `json:"chart_name" yaml:"chart_name"` ChartVersion string `json:"chart_version,omitempty" yaml:"chart_version,omitempty"` ReleaseName string `json:"release_name,omitempty" yaml:"release_name,omitempty"` @@ -114,6 +115,12 @@ type DeploymentLog struct { TargetLogID string `json:"target_log_id,omitempty" yaml:"target_log_id,omitempty"` } +// DeployResponse is the response from deploy/stop/clean operations (HTTP 202). +type DeployResponse struct { + LogID string `json:"log_id" yaml:"log_id"` + Message string `json:"message" yaml:"message"` +} + // RollbackRequest is the request body for POST /api/v1/stack-instances/:id/rollback. type RollbackRequest struct { TargetLogID string `json:"target_log_id,omitempty" yaml:"target_log_id,omitempty"` @@ -143,7 +150,7 @@ type ListResponse[T any] struct { Data []T `json:"data"` Total int `json:"total"` Page int `json:"page"` - PageSize int `json:"page_size"` + PageSize int `json:"pageSize"` TotalPages int `json:"total_pages"` } diff --git a/cli/test/integration/stack_integration_test.go b/cli/test/integration/stack_integration_test.go index 35eb972..b22303b 100644 --- a/cli/test/integration/stack_integration_test.go +++ b/cli/test/integration/stack_integration_test.go @@ -148,11 +148,9 @@ func startStackMockServer(t *testing.T, state *stackMockState) *httptest.Server inst.Status = "deploying" state.mu.Unlock() w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ - ID: "deploy-" + id, - InstanceID: id, - Action: "deploy", - Status: "started", + json.NewEncoder(w).Encode(types.DeployResponse{ + LogID: "deploy-" + id, + Message: "Deploy started", }) // Stop @@ -161,11 +159,9 @@ func startStackMockServer(t *testing.T, state *stackMockState) *httptest.Server inst.Status = "stopped" state.mu.Unlock() w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ - ID: "stop-" + id, - InstanceID: id, - Action: "stop", - Status: "started", + json.NewEncoder(w).Encode(types.DeployResponse{ + LogID: "stop-" + id, + Message: "Stop started", }) // Clean @@ -174,11 +170,9 @@ func startStackMockServer(t *testing.T, state *stackMockState) *httptest.Server inst.Status = "cleaned" state.mu.Unlock() w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(types.DeploymentLog{ - ID: "clean-" + id, - InstanceID: id, - Action: "clean", - Status: "started", + json.NewEncoder(w).Encode(types.DeployResponse{ + LogID: "clean-" + id, + Message: "Clean started", }) // Status @@ -302,10 +296,10 @@ func TestStackWorkflow_CreateDeployStatusLogsStopCleanDelete(t *testing.T) { assert.Equal(t, "lifecycle-stack", got.Name) // 3. Deploy - deployLog, err := c.DeployStack(id) + deployResp, err := c.DeployStack(id) require.NoError(t, err) - assert.Equal(t, "deploy", deployLog.Action) - assert.Equal(t, id, deployLog.InstanceID) + assert.Equal(t, "deploy-"+id, deployResp.LogID) + assert.NotEmpty(t, deployResp.Message) // 4. Status — should be deploying status, err := c.GetStackStatus(id) @@ -320,14 +314,14 @@ func TestStackWorkflow_CreateDeployStatusLogsStopCleanDelete(t *testing.T) { assert.Contains(t, log.Output, "completed successfully") // 6. Stop - stopLog, err := c.StopStack(id) + stopResp, err := c.StopStack(id) require.NoError(t, err) - assert.Equal(t, "stop", stopLog.Action) + assert.Equal(t, "stop-"+id, stopResp.LogID) // 7. Clean - cleanLog, err := c.CleanStack(id) + cleanResp, err := c.CleanStack(id) require.NoError(t, err) - assert.Equal(t, "clean", cleanLog.Action) + assert.Equal(t, "clean-"+id, cleanResp.LogID) // 8. Delete err = c.DeleteStack(id) diff --git a/cli/test/integration/template_definition_integration_test.go b/cli/test/integration/template_definition_integration_test.go index 5527ba5..9520c32 100644 --- a/cli/test/integration/template_definition_integration_test.go +++ b/cli/test/integration/template_definition_integration_test.go @@ -122,24 +122,18 @@ func startTemplateDefMockServer(t *testing.T, state *templateDefMockState) *http return } state.mu.Lock() - inst := &types.StackInstance{ - Base: types.Base{ID: fmt.Sprintf("%d", state.nextInstID), Version: "1"}, - Name: req.Name, - Branch: req.Branch, - Status: "draft", - Owner: "admin", - StackDefinitionID: "", - } - if req.ClusterID != "" { - cid := req.ClusterID - inst.ClusterID = &cid + def := &types.StackDefinition{ + Base: types.Base{ID: fmt.Sprintf("%d", state.nextDefID), Version: "1"}, + Name: req.Name, + DefaultBranch: req.Branch, + Owner: "admin", } - state.instances[inst.ID] = inst - state.nextInstID++ + state.definitions[def.ID] = def + state.nextDefID++ state.mu.Unlock() w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(inst) + json.NewEncoder(w).Encode(def) return case tmplAction == "quick-deploy" && r.Method == http.MethodPost: @@ -352,18 +346,17 @@ func TestTemplateWorkflow_BrowseAndInstantiate(t *testing.T) { assert.Len(t, tmpl.Charts, 2) // 4. Instantiate from template - instance, err := c.InstantiateTemplate("1", &types.InstantiateTemplateRequest{ + def, err := c.InstantiateTemplate("1", &types.InstantiateTemplateRequest{ Name: "my-web-app", Branch: "main", }) require.NoError(t, err) - assert.Equal(t, "my-web-app", instance.Name) - assert.Equal(t, "draft", instance.Status) - assert.Equal(t, "main", instance.Branch) + assert.Equal(t, "my-web-app", def.Name) + assert.Equal(t, "main", def.DefaultBranch) - // 5. Verify instance is stored + // 5. Verify definition is stored state.mu.Lock() - _, exists := state.instances[instance.ID] + _, exists := state.definitions[def.ID] state.mu.Unlock() assert.True(t, exists) }