Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/interactively invoke custom tasks #264

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
14 changes: 14 additions & 0 deletions cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"crypto/sha1"
"errors"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -53,3 +54,16 @@ func flagStringNullValueOrNil(flags *pflag.FlagSet, flag string) (*null.String,
// if not defined, return nil
return nil, nil
}

func splitInvokeTaskArguments(invokedTaskArguments []string) (map[string]string, error) {
parsedArgs := map[string]string{}

for _, v := range invokedTaskArguments {
split := strings.Split(v, "=")
if len(split) != 2 {
return map[string]string{}, errors.New(fmt.Sprintf("Unable to parse `%v`, the form of arguments should be `KEY=VALUE`", v))
}
parsedArgs[split[0]] = split[1]
}
return parsedArgs, nil
}
48 changes: 48 additions & 0 deletions cmd/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,51 @@ func Test_flagStringNullValueOrNil(t *testing.T) {
})
}
}

func Test_splitInvokeTaskArguments(t *testing.T) {
type args struct {
invokedTaskArguments []string
}
tests := []struct {
name string
args args
want map[string]string
wantErr bool
}{
{
name: "Standard parsing, single argument",
args: args{
invokedTaskArguments: []string{
"KEY1=VALUE1",
},
},
want: map[string]string{
"KEY1": "VALUE1",
},
wantErr: false,
},
{
name: "Invalid Input, multiple arguments",
args: args{
invokedTaskArguments: []string{
"KEY1=VALUE1",
"INVALID_ARGUMENT",
},
},
want: map[string]string{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := splitInvokeTaskArguments(tt.args.invokedTaskArguments)
if (err != nil) != tt.wantErr {
t.Errorf("splitInvokeTaskArguments() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("splitInvokeTaskArguments() got = %v, want %v", got, tt.want)
}
})
}
}
2 changes: 1 addition & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ func init() {
runCmd.AddCommand(runDrushCacheClear)
runCmd.AddCommand(runDrushSQLDump)
runCmd.AddCommand(runActiveStandbySwitch)
runCmd.AddCommand(invokeDefinedTask)
runCmd.AddCommand(runDefinedTask)
}
149 changes: 137 additions & 12 deletions cmd/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"github.com/uselagoon/lagoon-cli/internal/lagoon"
"github.com/uselagoon/lagoon-cli/internal/lagoon/client"
Expand Down Expand Up @@ -187,14 +188,14 @@ var runDrushCacheClear = &cobra.Command{
},
}

var invokeDefinedTask = &cobra.Command{
Use: "invoke",
var runDefinedTask = &cobra.Command{
Use: "task",
Aliases: []string{"i"},
Short: "",
Long: `Invoke a task registered against an environment
Short: "Run a custom task registered against an environment",
Long: `Run a custom task registered against an environment
The following are supported methods to use
Direct:
lagoon run invoke -p example -e main -N "advanced task name"
lagoon run task -p example -e main -N "advanced task name" [--argument=NAME=VALUE|..]
`,
Run: func(cmd *cobra.Command, args []string) {
if cmdProjectName == "" || cmdProjectEnvironment == "" || invokedTaskName == "" {
Expand All @@ -203,7 +204,127 @@ Direct:
os.Exit(1)
}

taskResult, err := eClient.InvokeAdvancedTaskDefinition(cmdProjectName, cmdProjectEnvironment, invokedTaskName)
taskArguments, err := splitInvokeTaskArguments(invokedTaskArguments)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}

taskResult, err := eClient.InvokeAdvancedTaskDefinition(cmdProjectName, cmdProjectEnvironment, invokedTaskName, taskArguments)
handleError(err)
var resultMap map[string]interface{}
err = json.Unmarshal([]byte(taskResult), &resultMap)
handleError(err)
resultData := output.Result{
Result: "success",
ResultData: resultMap,
}
output.RenderResult(resultData, outputOptions)
},
}

var invokeInteractiveTask = &cobra.Command{
Use: "interactive",
Aliases: []string{"i"},
Short: "Interactively run a custom task against an environment",
Long: `Interactively run a custom task against an environment
Provides prompts for arguments
example:
lagoon run task interactive -p example -e main
`,
Run: func(cmd *cobra.Command, args []string) {
debug, err := cmd.Flags().GetBool("debug")
if cmdProjectName == "" || cmdProjectEnvironment == "" {
fmt.Println("Missing arguments: Project name or environment name are not defined")
cmd.Help()
os.Exit(1)
}

current := lagoonCLIConfig.Current
lc := client.New(
lagoonCLIConfig.Lagoons[current].GraphQL,
lagoonCLIConfig.Lagoons[current].Token,
lagoonCLIConfig.Lagoons[current].Version,
lagoonCLIVersion,
debug)
project, err := lagoon.GetMinimalProjectByName(context.TODO(), cmdProjectName, lc)
if err != nil {
fmt.Println(err.Error())
return
}

environment, err := lagoon.TasksForEnvironment(context.TODO(), project.ID, cmdProjectEnvironment, lc)

if err != nil {
fmt.Println(err.Error())
return
}

if len(environment.AdvancedTasks) == 0 {
fmt.Printf("There are no custom tasks registered against %v %v\n", cmdProjectName, environment.Name)
return
}

prompt := promptui.Select{
Label: "Select",
Items: environment.AdvancedTasks,
Templates: &promptui.SelectTemplates{
Active: fmt.Sprintf("%s {{ .Name | underline }} -- {{ .Description }}", promptui.IconSelect),
Inactive: " {{ .Name }} -- {{ .Description }}",
Selected: fmt.Sprintf("Task: {{ .Name }}"),
},
}

taskIndex, _, err := prompt.Run()

if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}

task := environment.AdvancedTasks[taskIndex]
taskArguments := map[string]string{}
for _, v := range task.AdvancedTaskDefinitionArguments {
if len(v.Range) != 0 { //we have a selection
argPrompt := promptui.Select{
Label: fmt.Sprintf("%v", v.DisplayName),
Items: v.Range,
Templates: &promptui.SelectTemplates{
Active: fmt.Sprintf("%s {{ . | underline }}", promptui.IconSelect),
Inactive: " {{ . }}",
Selected: fmt.Sprintf("-- %s : {{ . }}", v.DisplayName),
},
}
_, argumentValue, err := argPrompt.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}
taskArguments[v.Name] = argumentValue
} else { // standard prompt
prompt := promptui.Prompt{
Label: fmt.Sprintf("%v", v.DisplayName),
Templates: &promptui.PromptTemplates{
Valid: fmt.Sprintf("-- {{ . }} : "),
Success: fmt.Sprintf("-- {{ . }} : "),
},
}
argumentValue, err := prompt.Run()

if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}
taskArguments[v.Name] = argumentValue
}
}

if !yesNo(fmt.Sprintf("Run above command on %s %s", cmdProjectName, cmdProjectEnvironment)) {
fmt.Println("Exiting")
return
}

taskResult, err := eClient.InvokeAdvancedTaskDefinition(cmdProjectName, cmdProjectEnvironment, task.Name, taskArguments)
handleError(err)
var resultMap map[string]interface{}
err = json.Unmarshal([]byte(taskResult), &resultMap)
Expand Down Expand Up @@ -277,15 +398,19 @@ Path:
}

var (
taskName string
invokedTaskName string
taskService string
taskCommand string
taskCommandFile string
taskName string
invokedTaskName string
invokedTaskArguments []string
taskService string
taskCommand string
taskCommandFile string
)

func init() {
invokeDefinedTask.Flags().StringVarP(&invokedTaskName, "name", "N", "", "Name of the task that will be invoked")
//register sub tasks
runDefinedTask.AddCommand(invokeInteractiveTask)
runDefinedTask.Flags().StringVarP(&invokedTaskName, "name", "N", "", "Name of the task that will be run")
runDefinedTask.Flags().StringSliceVar(&invokedTaskArguments, "argument", []string{}, "Arguments to be passed to custom task, of the form NAME=VALUE")
runCustomTask.Flags().StringVarP(&taskName, "name", "N", "Custom Task", "Name of the task that will show in the UI (default: Custom Task)")
runCustomTask.Flags().StringVarP(&taskService, "service", "S", "cli", "Name of the service (cli, nginx, other) that should run the task (default: cli)")
runCustomTask.Flags().StringVarP(&taskCommand, "command", "c", "", "The command to run in the task")
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/lagoon_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ Run a task against an environment
* [lagoon run drush-archivedump](lagoon_run_drush-archivedump.md) - Run a drush archive dump on an environment
* [lagoon run drush-cacheclear](lagoon_run_drush-cacheclear.md) - Run a drush cache clear on an environment
* [lagoon run drush-sqldump](lagoon_run_drush-sqldump.md) - Run a drush sql dump on an environment
* [lagoon run invoke](lagoon_run_invoke.md) -
* [lagoon run task](lagoon_run_task.md) - Run a custom task registered against an environment

46 changes: 46 additions & 0 deletions docs/commands/lagoon_run_task.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## lagoon run task

Run a custom task registered against an environment

### Synopsis

Run a custom task registered against an environment
The following are supported methods to use
Direct:
lagoon run task -p example -e main -N "advanced task name" [--argument=NAME=VALUE|..]


```
lagoon run task [flags]
```

### Options

```
--argument strings Arguments to be passed to custom task, of the form NAME=VALUE
-h, --help help for task
-N, --name string Name of the task that will be run
```

### Options inherited from parent commands

```
--config-file string Path to the config file to use (must be *.yml or *.yaml)
--debug Enable debugging output (if supported)
-e, --environment string Specify an environment to use
--force Force yes on prompts (if supported)
-l, --lagoon string The Lagoon instance to interact with
--no-header No header on table (if supported)
--output-csv Output as CSV (if supported)
--output-json Output as JSON (if supported)
--pretty Make JSON pretty (if supported)
-p, --project string Specify a project to use
--skip-update-check Skip checking for updates
-i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication
```

### SEE ALSO

* [lagoon run](lagoon_run.md) - Run a task against an environment
* [lagoon run task interactive](lagoon_run_task_interactive.md) - Interactively run a custom task against an environment

Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
## lagoon run invoke

## lagoon run task interactive

Interactively run a custom task against an environment

### Synopsis

Invoke a task registered against an environment
The following are supported methods to use
Direct:
lagoon run invoke -p example -e main -N "advanced task name"
Interactively run a custom task against an environment
Provides prompts for arguments
example:
lagoon run task interactive -p example -e main


```
lagoon run invoke [flags]
lagoon run task interactive [flags]
```

### Options

```
-h, --help help for invoke
-N, --name string Name of the task that will be invoked
-h, --help help for interactive
```

### Options inherited from parent commands
Expand All @@ -40,5 +39,5 @@ lagoon run invoke [flags]

### SEE ALSO

* [lagoon run](lagoon_run.md) - Run a task against an environment
* [lagoon run task](lagoon_run_task.md) - Run a custom task registered against an environment

Loading