From 6fb869a153b8a94d90c1ab2112263ee4ef575e0a Mon Sep 17 00:00:00 2001 From: Mohammed Diaa Date: Tue, 30 Jan 2024 17:50:40 +0200 Subject: [PATCH 1/5] Add private script support --- cmd/library/library.go | 23 ++++++ cmd/root.go | 2 + cmd/scripts/scripts.go | 131 +++++++++++++++++++++++++++++++++++ cmd/scripts/scriptsCreate.go | 37 ++++++++++ cmd/scripts/scriptsDelete.go | 64 +++++++++++++++++ cmd/scripts/scriptsList.go | 44 ++++++++++++ cmd/scripts/scriptsUpdate.go | 35 ++++++++++ types/scripts.go | 13 ++++ 8 files changed, 349 insertions(+) create mode 100644 cmd/scripts/scripts.go create mode 100644 cmd/scripts/scriptsCreate.go create mode 100644 cmd/scripts/scriptsDelete.go create mode 100644 cmd/scripts/scriptsList.go create mode 100644 cmd/scripts/scriptsUpdate.go create mode 100644 types/scripts.go diff --git a/cmd/library/library.go b/cmd/library/library.go index 5214fd9..b49b392 100644 --- a/cmd/library/library.go +++ b/cmd/library/library.go @@ -56,3 +56,26 @@ func PrintTools(tools []types.Tool, jsonOutput bool) { fmt.Println(output) } + +func PrintScripts(scripts []types.Script, jsonOutput bool) { + var output string + if jsonOutput { + data, err := json.Marshal(scripts) + if err != nil { + fmt.Println("Error marshalling project data") + return + } + output = string(data) + } else { + tree := treeprint.New() + tree.SetValue("Scripts") + for _, script := range scripts { + branch := tree.AddBranch(script.Name) + branch.AddNode(script.Script.Source) + } + + output = tree.String() + } + + fmt.Println(output) +} diff --git a/cmd/root.go b/cmd/root.go index d95214a..4281907 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,6 +11,7 @@ import ( "github.com/trickest/trickest-cli/cmd/library" "github.com/trickest/trickest-cli/cmd/list" "github.com/trickest/trickest-cli/cmd/output" + "github.com/trickest/trickest-cli/cmd/scripts" "github.com/trickest/trickest-cli/cmd/tools" "github.com/trickest/trickest-cli/util" @@ -54,6 +55,7 @@ func init() { RootCmd.AddCommand(get.GetCmd) RootCmd.AddCommand(files.FilesCmd) RootCmd.AddCommand(tools.ToolsCmd) + RootCmd.AddCommand(scripts.ScriptsCmd) // RootCmd.AddCommand(export.ExportCmd) } diff --git a/cmd/scripts/scripts.go b/cmd/scripts/scripts.go new file mode 100644 index 0000000..d07fe3a --- /dev/null +++ b/cmd/scripts/scripts.go @@ -0,0 +1,131 @@ +package scripts + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + "github.com/go-yaml/yaml" + "github.com/google/uuid" + "github.com/spf13/cobra" + "github.com/trickest/trickest-cli/client/request" + "github.com/trickest/trickest-cli/types" + "github.com/trickest/trickest-cli/util" +) + +var ScriptsCmd = &cobra.Command{ + Use: "scripts", + Short: "Manage private scripts", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +func init() { + ScriptsCmd.SetHelpFunc(func(command *cobra.Command, strings []string) { + _ = ScriptsCmd.Flags().MarkHidden("workflow") + _ = ScriptsCmd.Flags().MarkHidden("project") + _ = ScriptsCmd.Flags().MarkHidden("space") + _ = ScriptsCmd.Flags().MarkHidden("url") + + command.Root().HelpFunc()(command, strings) + }) +} + +func ListPrivateScripts(name string) ([]types.Script, error) { + endpoint := fmt.Sprintf("script/?vault=%s", util.GetVault()) + if name != "" { + endpoint += "&search=" + name + } else { + endpoint += "&page_size=100" + } + + resp := request.Trickest.Get().Do(endpoint) + if resp == nil || resp.Status() != http.StatusOK { + request.ProcessUnexpectedResponse(resp) + } + + var scripts types.Scripts + err := json.Unmarshal(resp.Body(), &scripts) + if err != nil { + return nil, fmt.Errorf("couldn't parse API response: %s", err) + } + + return scripts.Results, nil +} + +func getScriptIDByName(name string) (uuid.UUID, error) { + scripts, err := ListPrivateScripts(name) + if err != nil { + return uuid.Nil, fmt.Errorf("couldn't search for %s: %w", name, err) + } + + for _, script := range scripts { + if script.Name == name { + return script.ID, nil + } + } + + return uuid.Nil, fmt.Errorf("couldn't find script '%s'", name) +} + +func createScriptImportRequestFromYAML(fileName string) (types.ScriptImportRequest, error) { + data, err := os.ReadFile(fileName) + if err != nil { + err = fmt.Errorf("couldn't read %s: %w", fileName, err) + return types.ScriptImportRequest{}, err + } + + var scriptImportRequest types.ScriptImportRequest + err = yaml.Unmarshal(data, &scriptImportRequest) + if err != nil { + err = fmt.Errorf("couldn't parse %s: %w", fileName, err) + return types.ScriptImportRequest{}, err + } + + scriptImportRequest.VaultInfo = util.GetVault() + + return scriptImportRequest, nil +} + +func importScript(fileName string, isUpdate bool) (string, uuid.UUID, error) { + scriptImportRequest, err := createScriptImportRequestFromYAML(fileName) + if err != nil { + return "", uuid.Nil, err + } + + scriptJSON, err := json.Marshal(scriptImportRequest) + if err != nil { + return "", uuid.Nil, fmt.Errorf("couldn't encode %s: %w", fileName, err) + } + + var resp *request.Response + if isUpdate { + scriptName := scriptImportRequest.Name + scriptID, err := getScriptIDByName(scriptName) + if err != nil { + return "", uuid.Nil, fmt.Errorf("couldn't import '%s': %w", scriptName, err) + } + resp = request.Trickest.Patch().Body(scriptJSON).DoF("script/%s/", scriptID.String()) + } else { + resp = request.Trickest.Post().Body(scriptJSON).Do("script/") + } + + if resp == nil { + return "", uuid.Nil, fmt.Errorf("couldn't import %s", fileName) + } + + if resp.Status() != http.StatusCreated && resp.Status() != http.StatusOK { + request.ProcessUnexpectedResponse(resp) + } + + var importedScript types.ScriptImportRequest + err = json.Unmarshal(resp.Body(), &importedScript) + if err != nil { + return "", uuid.Nil, fmt.Errorf("couldn't import %s: %w", fileName, err) + } + + return importedScript.Name, *importedScript.ID, nil +} diff --git a/cmd/scripts/scriptsCreate.go b/cmd/scripts/scriptsCreate.go new file mode 100644 index 0000000..c759125 --- /dev/null +++ b/cmd/scripts/scriptsCreate.go @@ -0,0 +1,37 @@ +package scripts + +import ( + "fmt" + "os" + + "github.com/google/uuid" + "github.com/spf13/cobra" +) + +var file string + +var scriptsCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new private script", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + name, id, err := createScript(file) + if err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + + fmt.Printf("Succesfuly imported %s (%s)\n", name, id) + }, +} + +func init() { + ScriptsCmd.AddCommand(scriptsCreateCmd) + + scriptsCreateCmd.Flags().StringVar(&file, "file", "", "YAML file for script definition") + scriptsCreateCmd.MarkFlagRequired("file") +} + +func createScript(fileName string) (string, uuid.UUID, error) { + return importScript(fileName, false) +} diff --git a/cmd/scripts/scriptsDelete.go b/cmd/scripts/scriptsDelete.go new file mode 100644 index 0000000..95c2f67 --- /dev/null +++ b/cmd/scripts/scriptsDelete.go @@ -0,0 +1,64 @@ +package scripts + +import ( + "fmt" + "net/http" + "os" + + "github.com/spf13/cobra" + "github.com/trickest/trickest-cli/client/request" +) + +var ( + scriptID string + scriptName string +) + +var scriptsDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete a private script", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + if scriptName == "" && scriptID == "" { + cmd.Help() + return + } + + if scriptName != "" { + id, err := getScriptIDByName(scriptName) + if err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + scriptID = id.String() + } + + err := deleteScript(scriptID) + if err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + + fmt.Printf("Succesfuly deleted %s\n", scriptID) + }, +} + +func init() { + ScriptsCmd.AddCommand(scriptsDeleteCmd) + + scriptsDeleteCmd.Flags().StringVar(&scriptID, "id", "", "ID of the script to delete") + scriptsDeleteCmd.Flags().StringVar(&scriptName, "name", "", "Name of the script to delete") +} + +func deleteScript(scriptID string) error { + resp := request.Trickest.Delete().DoF("script/%s/", scriptID) + if resp == nil { + return fmt.Errorf("couldn't delete %s: invalid response", scriptID) + } + + if resp.Status() == http.StatusNoContent { + return nil + } else { + return fmt.Errorf("couldn't delete %s: unexpected status code (%d)", scriptID, resp.Status()) + } +} diff --git a/cmd/scripts/scriptsList.go b/cmd/scripts/scriptsList.go new file mode 100644 index 0000000..f7bc952 --- /dev/null +++ b/cmd/scripts/scriptsList.go @@ -0,0 +1,44 @@ +package scripts + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/trickest/trickest-cli/cmd/library" +) + +var jsonOutput bool + +var scriptsListCmd = &cobra.Command{ + Use: "list", + Short: "List private scripts", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + err := listScripts(jsonOutput) + if err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + }, +} + +func init() { + ScriptsCmd.AddCommand(scriptsListCmd) + + scriptsListCmd.Flags().BoolVar(&jsonOutput, "json", false, "Display output in JSON format") +} + +func listScripts(jsonOutput bool) error { + scripts, err := ListPrivateScripts("") + if err != nil { + return fmt.Errorf("couldn't list private scripts: %w", err) + } + + if len(scripts) == 0 { + return fmt.Errorf("couldn't find any private scripts. Did you mean `library list scripts`?") + } + + library.PrintScripts(scripts, jsonOutput) + return nil +} diff --git a/cmd/scripts/scriptsUpdate.go b/cmd/scripts/scriptsUpdate.go new file mode 100644 index 0000000..fbc72bd --- /dev/null +++ b/cmd/scripts/scriptsUpdate.go @@ -0,0 +1,35 @@ +package scripts + +import ( + "fmt" + "os" + + "github.com/google/uuid" + "github.com/spf13/cobra" +) + +var scriptsUpdateCmd = &cobra.Command{ + Use: "update", + Short: "Update a private script", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + name, id, err := updateScript(file) + if err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + + fmt.Printf("Succesfuly imported %s (%s)\n", name, id) + }, +} + +func init() { + ScriptsCmd.AddCommand(scriptsUpdateCmd) + + scriptsUpdateCmd.Flags().StringVar(&file, "file", "", "YAML file for script definition") + scriptsUpdateCmd.MarkFlagRequired("file") +} + +func updateScript(fileName string) (string, uuid.UUID, error) { + return importScript(fileName, true) +} diff --git a/types/scripts.go b/types/scripts.go new file mode 100644 index 0000000..f27450d --- /dev/null +++ b/types/scripts.go @@ -0,0 +1,13 @@ +package types + +import "github.com/google/uuid" + +type ScriptImportRequest struct { + ID *uuid.UUID `json:"id,omitempty"` + VaultInfo uuid.UUID `json:"vault_info" yaml:"vault_info"` + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"description"` + DockerImage string `json:"docker_image" yaml:"docker_image"` + Script string `json:"script" yaml:"script"` + Command string `json:"command" yaml:"command"` +} From cc33dce129e1f10d611f0b325df30a271d51b302 Mon Sep 17 00:00:00 2001 From: Mohammed Diaa Date: Tue, 30 Jan 2024 18:06:57 +0200 Subject: [PATCH 2/5] Refactor object name to ID code --- cmd/tools/tools.go | 12 +++++------- util/util.go | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/cmd/tools/tools.go b/cmd/tools/tools.go index 3b640b0..bef5610 100644 --- a/cmd/tools/tools.go +++ b/cmd/tools/tools.go @@ -69,15 +69,13 @@ func getToolIDByName(name string) (uuid.UUID, error) { return uuid.Nil, fmt.Errorf("couldn't search for %s: %w", name, err) } - if len(tools) == 0 { - return uuid.Nil, fmt.Errorf("couldn't find tool '%s'", name) - } - - if len(tools) > 1 { - return uuid.Nil, fmt.Errorf("found more than one match for '%s'", name) + for _, tool := range tools { + if tool.Name == name { + return tool.ID, nil + } } - return tools[0].ID, nil + return uuid.Nil, fmt.Errorf("couldn't find tool '%s'", name) } func createToolImportRequestFromYAML(fileName string) (types.ToolImportRequest, error) { diff --git a/util/util.go b/util/util.go index 241b7e1..6a9079d 100644 --- a/util/util.go +++ b/util/util.go @@ -569,13 +569,11 @@ func GetCategoryIDByName(name string) (uuid.UUID, error) { return uuid.Nil, fmt.Errorf("couldn't search for %s: %w", name, err) } - if len(categories) == 0 { - return uuid.Nil, fmt.Errorf("couldn't find category '%s'", name) - } - - if len(categories) > 1 { - return uuid.Nil, fmt.Errorf("found more than one match for '%s'", name) + for _, category := range categories { + if category.Name == name { + return category.ID, nil + } } - return categories[0].ID, nil + return uuid.Nil, fmt.Errorf("couldn't find category '%s'", name) } From 44bc185d0cbff7cdb442376601c88c7690f3b745 Mon Sep 17 00:00:00 2001 From: Mohammed Diaa Date: Tue, 30 Jan 2024 18:10:44 +0200 Subject: [PATCH 3/5] Update README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 8d6e0d6..ba593c1 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,50 @@ trickest tools delete --name "my-tool" | --id | string | / | ID of the tool to delete | | --name | string | / | Name of the tool to delete | + +## Scripts command +Manage [private scripts](#) + +⚒️ Learn how to add your first private script [here](#). + +#### Create a new private script +``` +trickest scripts create --file script.yaml +``` + +| Flag | Type | Default | Description | +|----------------------|--------|----------|---------------------------------------------------------------------| +| --file | string | / | YAML file for script definition | + +#### Update a private script +``` +trickest scripts update --file script.yaml +``` + +| Flag | Type | Default | Description | +|----------------------|--------|----------|---------------------------------------------------------------------| +| --file | string | / | YAML file for script definition | + +#### List private scripts +``` +trickest scripts list +``` + +| Flag | Type | Default | Description | +|----------------------|---------|-------------|---------------------------------------------------------------------| +| --json | boolean | false | Display output in JSON format | + +#### Delete a private script +``` +trickest scripts delete --name "my-script" +``` + +| Flag | Type | Default | Description | +|----------------------|--------|----------|---------------------------------------------------------------------| +| --id | string | / | ID of the script to delete | +| --name | string | / | Name of the script to delete | + + ## Report Bugs / Feedback We look forward to any feedback you want to share with us or if you're stuck with a problem you can contact us at [support@trickest.com](mailto:support@trickest.com). From 8fd69dc89017012bff966b216479baa099ea843b Mon Sep 17 00:00:00 2001 From: Mohammed Diaa Date: Fri, 2 Feb 2024 18:18:37 +0200 Subject: [PATCH 4/5] Update script definition --- README.md | 8 +++++++- types/scripts.go | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ba593c1..8c639b0 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,13 @@ trickest tools delete --name "my-tool" ## Scripts command Manage [private scripts](#) -⚒️ Learn how to add your first private script [here](#). +#### Example script definition +```yaml +name: hello-world +description: Write "Hello, world!" to the output +script_type: bash +script: echo "Hello, world!" | tee out/output.txt +``` #### Create a new private script ``` diff --git a/types/scripts.go b/types/scripts.go index f27450d..30f97e9 100644 --- a/types/scripts.go +++ b/types/scripts.go @@ -7,7 +7,6 @@ type ScriptImportRequest struct { VaultInfo uuid.UUID `json:"vault_info" yaml:"vault_info"` Name string `json:"name" yaml:"name"` Description string `json:"description" yaml:"description"` - DockerImage string `json:"docker_image" yaml:"docker_image"` + ScriptType string `json:"script_type" yaml:"script_type"` Script string `json:"script" yaml:"script"` - Command string `json:"command" yaml:"command"` } From 6a6a7e581bc96f3b7da6ebdabd587f49156ef60a Mon Sep 17 00:00:00 2001 From: Mohammed Diaa Date: Tue, 6 Feb 2024 19:03:41 +0200 Subject: [PATCH 5/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c639b0..0a0ff49 100644 --- a/README.md +++ b/README.md @@ -311,7 +311,7 @@ trickest tools delete --name "my-tool" ## Scripts command -Manage [private scripts](#) +Manage private scripts #### Example script definition ```yaml