Skip to content

Commit

Permalink
feat: unpolished deploy cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
0michalsokolowski0 committed Sep 27, 2024
1 parent 91b84fa commit c4ac4c0
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 58 deletions.
11 changes: 11 additions & 0 deletions internal/cmd/blueprint/blueprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ func Command() *cli.Command {
Before: cmd.PerformAllBefore(cmd.HandleNoColor, authenticated.Ensure),
ArgsUsage: cmd.EmptyArgsUsage,
},
{
Name: "deploy",
Usage: "Deploy a stack from the blueprint",
Flags: []cli.Flag{
flagRequiredBlueprintID,
cmd.FlagNoColor,
},
Action: (&deployCommand{}).deploy,
Before: cmd.PerformAllBefore(cmd.HandleNoColor, authenticated.Ensure),
ArgsUsage: cmd.EmptyArgsUsage,
},
},
}
}
Expand Down
190 changes: 190 additions & 0 deletions internal/cmd/blueprint/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package blueprint

import (
"fmt"
"slices"
"strconv"
"strings"

"github.com/manifoldco/promptui"
"github.com/pkg/errors"
"github.com/spacelift-io/spacectl/internal/cmd/authenticated"
"github.com/urfave/cli/v2"
)

type deployCommand struct{}

func (c *deployCommand) deploy(cliCtx *cli.Context) error {
blueprintID := cliCtx.String(flagRequiredBlueprintID.Name)

b, found, err := getBlueprintByID(cliCtx.Context, blueprintID)
if err != nil {
return errors.Wrapf(err, "failed to query for blueprint ID %q", blueprintID)
}

if !found {
return fmt.Errorf("blueprint with ID %q not found", blueprintID)
}

templateInputs := make([]BlueprintStackCreateInputPair, 0, len(b.Inputs))

for _, input := range b.Inputs {
var value string
switch strings.ToLower(input.Type) {
case "", "short_text", "long_text":
value, err = promptForTextInput(input)
if err != nil {
return err
}
case "secret":
value, err = promptForSecretInput(input)
if err != nil {
return err
}
case "number":
value, err = promptForIntegerInput(input)
if err != nil {
return err
}
case "float":
value, err = promptForFloatInput(input)
if err != nil {
return err
}
case "boolean":
value, err = promptForSelectInput(input, []string{"true", "false"})
if err != nil {
return err
}
case "select":
value, err = promptForSelectInput(input, input.Options)
if err != nil {
return err
}
}

templateInputs = append(templateInputs, BlueprintStackCreateInputPair{
ID: input.ID,
Value: value,
})
}

var mutation struct {
BlueprintCreateStack struct {
StackID string `graphql:"stackID"`
RunID string `graphql:"runID"`
} `graphql:"blueprintCreateStack(id: $id, input: $input)"`
}

err = authenticated.Client.Mutate(
cliCtx.Context,
&mutation,
map[string]any{
"id": blueprintID,
"input": BlueprintStackCreateInput{
TemplateInputs: templateInputs,
},
},
)
if err != nil {
return fmt.Errorf("failed to deploy stack from the blueprint: %w", err)
}

url := authenticated.Client.URL("/stack/%s", mutation.BlueprintCreateStack.StackID)
fmt.Printf("Created stack can found: %q", url)

return nil
}

func formatLabel(input blueprintInput) string {
if input.Description != "" {
return fmt.Sprintf("%s (%s) - %s", input.Name, input.ID, input.Description)
}
return fmt.Sprintf("%s (%s)", input.Name, input.ID)
}

func promptForTextInput(input blueprintInput) (string, error) {
prompt := promptui.Prompt{
Label: formatLabel(input),
Default: input.Default,
IsConfirm: input.Default != "",
}
result, err := prompt.Run()
if err != nil {
return "", fmt.Errorf("failed to read text input for %q: %w", input.Name, err)
}

return result, nil
}

func promptForSecretInput(input blueprintInput) (string, error) {
prompt := promptui.Prompt{
Label: formatLabel(input),
}
result, err := prompt.Run()
if err != nil {
return "", fmt.Errorf("failed to read secret input for %q: %w", input.Name, err)
}

return result, nil
}

func promptForIntegerInput(input blueprintInput) (string, error) {
prompt := promptui.Prompt{
Label: formatLabel(input),
Validate: func(s string) error {
_, err := strconv.Atoi(s)
if err != nil {
return fmt.Errorf("input must be an integer")
}

return nil
},
}
result, err := prompt.Run()
if err != nil {
return "", fmt.Errorf("failed to read integer input for %q: %w", input.Name, err)
}

return result, nil
}

func promptForFloatInput(input blueprintInput) (string, error) {
prompt := promptui.Prompt{
Label: formatLabel(input),
Validate: func(s string) error {
_, err := strconv.ParseFloat(s, 64)
if err != nil {
return fmt.Errorf("input must be a float")
}

return nil
},
}
result, err := prompt.Run()
if err != nil {
return "", fmt.Errorf("failed to read float input for %q: %w", input.Name, err)
}

return result, nil
}

func promptForSelectInput(input blueprintInput, options []string) (string, error) {
cursorPosition := 0
if input.Default != "" {
cursorPosition = slices.Index(options, input.Default)
}

sel := promptui.Select{
Label: formatLabel(input),
Items: options,
CursorPos: cursorPosition,
}

_, result, err := sel.Run()
if err != nil {
return "", fmt.Errorf("failed to read selected input for %q: %w", input.Name, err)
}

return result, nil
}
12 changes: 12 additions & 0 deletions internal/cmd/blueprint/inputs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package blueprint

// BlueprintStackCreateInputPair represents a key-value pair for a blueprint input.
type BlueprintStackCreateInputPair struct {
ID string `json:"id"`
Value string `json:"value"`
}

// BlueprintStackCreateInput represents the input for creating a new stack from a blueprint.
type BlueprintStackCreateInput struct {
TemplateInputs []BlueprintStackCreateInputPair `json:"templateInputs"`
}
Loading

0 comments on commit c4ac4c0

Please sign in to comment.