Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 56 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Configuration values are resolved in this order (highest priority first):
<img src="assets/stackctl-stack.svg" alt="stackctl stack commands" width="700">
</p>

All stack commands accept a **name or ID** — e.g. `stackctl stack deploy my-app` or `stackctl stack deploy 42`.

```bash
# List instances
stackctl stack list
Expand All @@ -138,22 +140,27 @@ stackctl stack list --cluster 1 -o json

# Create and deploy
stackctl stack create --definition 1 --name my-app --branch feature/xyz --ttl 480
stackctl stack deploy 42
stackctl stack deploy my-app

# Monitor
stackctl stack status 42
stackctl stack logs 42
stackctl stack status my-app
stackctl stack logs my-app

# Lifecycle
stackctl stack stop 42
stackctl stack clean 42
stackctl stack delete 42
stackctl stack stop my-app
stackctl stack clean my-app
stackctl stack delete my-app

# Clone an existing instance
stackctl stack clone 42
stackctl stack clone my-app

# Extend TTL
stackctl stack extend 42 --minutes 120
stackctl stack extend my-app --minutes 120

# Deployment history and rollback
stackctl stack history my-app
stackctl stack history-values my-app <log-id>
stackctl stack rollback my-app --target <log-id>
```

### Templates
Expand All @@ -168,6 +175,9 @@ stackctl template quick-deploy 1

# Or step by step
stackctl template instantiate 1 --name my-stack --branch main

# Delete a template
stackctl template delete 1
```

### Stack Definitions
Expand All @@ -180,6 +190,20 @@ stackctl definition get 5
# Create from file
stackctl definition create --from-file definition.json

# Update metadata
stackctl definition update 5 --name new-name
stackctl definition update 5 --branch develop
stackctl definition update 5 --description "Updated description"

# Update a chart config (GET-merge-PUT preserves unspecified fields)
stackctl definition update-chart 5 1 --chart-version 0.3.0
stackctl definition update-chart 5 1 --chart-path /charts/kvk-core
stackctl definition update-chart 5 1 --deploy-order 6
stackctl definition update-chart 5 1 --file values.yaml

# Delete
stackctl definition delete 5

# Export / import
stackctl definition export 5 > backup.json
stackctl definition import --file backup.json
Expand Down Expand Up @@ -212,17 +236,32 @@ stackctl stack compare 42 43

### Bulk Operations

Bulk commands accept **names or IDs** (up to 50 at a time).

```bash
# Bulk deploy/stop/clean/delete (up to 50 instances)
stackctl bulk deploy --ids 1,2,3,4,5
stackctl bulk deploy 1 2 3 4 5 # positional args also work
# Bulk deploy/stop/clean/delete
stackctl bulk deploy --ids my-app,other-app,3
stackctl bulk deploy my-app other-app 3 # positional args also work
stackctl bulk stop --ids 1,2,3
stackctl bulk clean --ids 1,2,3

# Piping workflows with quiet mode
stackctl stack list --status stopped --mine -q | xargs stackctl bulk deploy
```

### Orphaned Namespaces

Manage Kubernetes namespaces that have the stack-manager label but no matching database record.

```bash
# List orphaned namespaces
stackctl orphaned list
stackctl orphaned list -o json

# Delete an orphaned namespace
stackctl orphaned delete stack-old-namespace
```

### Scripting Examples

```bash
Expand Down Expand Up @@ -326,11 +365,13 @@ cli/
cmd/ # Cobra commands (one file per command group)
config.go # config set/get/list/use-context/current-context/delete-context
login.go # login, logout, whoami
stack.go # stack lifecycle (13 subcommands)
template.go # template list/get/instantiate/quick-deploy
definition.go # definition CRUD + export/import
stack.go # stack lifecycle (create, deploy, stop, clean, delete, clone, extend, status, logs, history, rollback, compare, values)
template.go # template list/get/instantiate/quick-deploy/delete
definition.go # definition CRUD + export/import + update-chart
override.go # value, branch, and quota overrides
bulk.go # bulk deploy/stop/clean/delete
orphaned.go # orphaned namespace list/delete
bulk.go # bulk deploy/stop/clean/delete (names or IDs)
resolve.go # name/ID resolution helpers
git.go # git branches/validate
cluster.go # cluster list/get
completion.go # shell completion (bash/zsh/fish/powershell)
Expand Down
127 changes: 125 additions & 2 deletions cli/cmd/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ var definitionUpdateCmd = &cobra.Command{

Examples:
stackctl definition update 1 --name new-name
stackctl definition update 1 --branch develop
stackctl definition update 1 --from-file definition.json`,
Args: cobra.ExactArgs(1),
SilenceUsage: true,
Expand All @@ -201,9 +202,10 @@ Examples:
fromFile, _ := cmd.Flags().GetString(flagFromFile)
name, _ := cmd.Flags().GetString("name")
description, _ := cmd.Flags().GetString("description")
branch, _ := cmd.Flags().GetString("branch")

if fromFile == "" && name == "" && description == "" {
return fmt.Errorf("at least one of --name, --description, or --from-file must be specified")
if fromFile == "" && name == "" && description == "" && branch == "" {
return fmt.Errorf("at least one of --name, --description, --branch, or --from-file must be specified")
}

var req types.UpdateDefinitionRequest
Expand All @@ -228,6 +230,9 @@ Examples:
if description != "" {
req.Description = description
}
if branch != "" {
req.DefaultBranch = branch
}
}

c, err := newClient()
Expand Down Expand Up @@ -368,6 +373,116 @@ Examples:
},
}

var definitionUpdateChartCmd = &cobra.Command{
Use: "update-chart <definition-id> <chart-id>",
Short: "Update a chart config within a definition",
Long: `Update a chart configuration's settings within a stack definition.

The command fetches the current chart config and merges your changes,
so unspecified fields are preserved.

Examples:
stackctl definition update-chart 1 5 --chart-version 0.3.0
stackctl definition update-chart 1 5 --chart-path /charts/kvk-core
stackctl definition update-chart 1 5 --deploy-order 6
stackctl definition update-chart 1 5 --file values.yaml`,
Args: cobra.ExactArgs(2),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
defID, err := parseID(args[0])
if err != nil {
return fmt.Errorf("invalid definition ID: %w", err)
}
chartID, err := parseID(args[1])
if err != nil {
return fmt.Errorf("invalid chart ID: %w", err)
}

chartPath, _ := cmd.Flags().GetString("chart-path")
chartVersion, _ := cmd.Flags().GetString("chart-version")
deployOrder, _ := cmd.Flags().GetInt("deploy-order")
valuesFile, _ := cmd.Flags().GetString("file")

if chartPath == "" && chartVersion == "" && deployOrder < 0 && valuesFile == "" {
return fmt.Errorf("at least one of --chart-path, --chart-version, --deploy-order, or --file must be specified")
}

if valuesFile != "" {
for _, segment := range strings.Split(filepath.ToSlash(valuesFile), "/") {
if segment == ".." {
return errors.New(msgPathTraversal)
}
}
}

c, err := newClient()
if err != nil {
return err
}

current, err := c.GetDefinitionChart(defID, chartID)
if err != nil {
return fmt.Errorf("fetching current chart config: %w", err)
}

req := types.UpdateChartConfigRequest{
ChartName: current.ChartName,
ChartPath: current.RepoURL,
ChartVersion: current.ChartVersion,
DefaultValues: current.DefaultValues,
}

if chartPath != "" {
req.ChartPath = chartPath
}
if chartVersion != "" {
req.ChartVersion = chartVersion
}
if deployOrder >= 0 {
req.DeployOrder = &deployOrder
}
if valuesFile != "" {
valuesFile = filepath.Clean(valuesFile)
data, err := os.ReadFile(valuesFile)
if err != nil {
return readFileErr(valuesFile, err)
}
req.DefaultValues = string(data)
}

updated, err := c.UpdateDefinitionChart(defID, chartID, &req)
if err != nil {
return err
}

return printChartConfig(updated)
},
}

func printChartConfig(ch *types.ChartConfig) error {
if printer.Quiet {
fmt.Fprintln(printer.Writer, ch.ID)
return nil
}

switch printer.Format {
case output.FormatJSON:
return printer.PrintJSON(ch)
case output.FormatYAML:
return printer.PrintYAML(ch)
default:
fields := []output.KeyValue{
{Key: "ID", Value: ch.ID},
{Key: "Name", Value: ch.Name},
{Key: "Chart", Value: ch.ChartName},
{Key: "Repository", Value: ch.RepoURL},
{Key: "Version", Value: ch.ChartVersion},
{Key: "Release Name", Value: ch.ReleaseName},
}
return printer.PrintSingle(ch, fields)
}
}

// printDefinition prints a stack definition in the configured output format.
func printDefinition(def *types.StackDefinition) error {
if printer.Quiet {
Expand Down Expand Up @@ -412,8 +527,15 @@ func init() {
// definition update flags
definitionUpdateCmd.Flags().String("name", "", "New definition name")
definitionUpdateCmd.Flags().String("description", "", "New definition description")
definitionUpdateCmd.Flags().String("branch", "", "New default branch")
definitionUpdateCmd.Flags().String(flagFromFile, "", "Update from JSON file")

// definition update-chart flags
definitionUpdateChartCmd.Flags().String("chart-path", "", "Chart path (e.g. /charts/kvk-core)")
definitionUpdateChartCmd.Flags().String("chart-version", "", "Chart version")
definitionUpdateChartCmd.Flags().Int("deploy-order", -1, "Deploy order (0+)")
definitionUpdateChartCmd.Flags().String("file", "", "File containing default values")

// definition delete flags
definitionDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")

Expand All @@ -432,6 +554,7 @@ func init() {
definitionCmd.AddCommand(definitionDeleteCmd)
definitionCmd.AddCommand(definitionExportCmd)
definitionCmd.AddCommand(definitionImportCmd)
definitionCmd.AddCommand(definitionUpdateChartCmd)
rootCmd.AddCommand(definitionCmd)
}

Expand Down
Loading
Loading