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

Add private tools support #108

Merged
merged 5 commits into from
Jan 15, 2024
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
64 changes: 53 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ trickest list --project <project_name> --space <space_name>
|-----------|---------|---------|----------------------------------------------------|
| --project | string | / | The name of the project to be listed. |
| --space | string | / | The name of the space to which the project belongs |
| --json | boolean | / | Display output in JSON format |
| --json | boolean | false | Display output in JSON format |
| --url | string | / | URL for referencing a space |


Expand All @@ -109,8 +109,8 @@ trickest get --workflow <workflow_name> --space <space_name> [--watch]
| --project | string | / | The name of the project to which the workflow belongs |
| --workflow | string | / | The name of the workflow |
| --run | string | / | Get the status of a specific run |
| --watch | boolean | / | Option to track execution status in case workflow is in running state |
| --json | boolean | / | Display output in JSON format |
| --watch | boolean | false | Option to track execution status in case workflow is in running state |
| --json | boolean | false | Display output in JSON format |
| --url | string | / | URL for referencing a space |

##### If the supplied workflow has a running execution, you can jump in and watch it running with the `--watch` flag!
Expand All @@ -126,12 +126,12 @@ trickest execute --workflow <workflow_or_tool_name> --space <space_name> --confi
|------------------|---------|---------|---------------------------------------------------------------------------------------------------------------------------------------------|
| --config | file | / | YAML file for run configuration |
| --workflow | string | / | Workflow from the Library to be executed |
| --max | boolean | / | Use maximum number of machines for workflow execution |
| --max | boolean | false | Use maximum number of machines for workflow execution |
| --output | string | / | A comma-separated list of nodes whose outputs should be downloaded when the execution is finished |
| --output-all | boolean | / | Download all outputs when the execution is finished |
| --output-all | boolean | false | Download all outputs when the execution is finished |
| --output-dir | string | . | Path to the directory which should be used to store outputs |
| --show-params | boolean | / | Show parameters in the workflow tree |
| --watch | boolean | / | Option to track execution status in case workflow is in running state |
| --show-params | boolean | false | Show parameters in the workflow tree |
| --watch | boolean | false | Option to track execution status in case workflow is in running state |
| --set-name | string | / | Sets the new workflow name and will copy the workflow to space and project supplied |
| --ci | boolean | false | Enable CI mode (in-progress executions will be stopped when the CLI is forcefully stopped - if not set, you will be asked for confirmation) |
| --create-project | boolean | false | If the project doesn't exist, create one using the project flag as its name (or workflow/tool name if project flag is not set) |
Expand Down Expand Up @@ -227,9 +227,6 @@ Use **library search** to search all Trickest tools & workflows available in the
trickest library search subdomain takeover
```

[<img src="./banner.png" />](https://trickest.io/auth/register)


## Files command
Interact with the Trickest file storage

Expand All @@ -244,7 +241,7 @@ trickest files get --file my_file.txt --output-dir out
|----------------------|--------|----------|---------------------------------------------------------------------|
| --file | string | / | File or files (comma-separated) |
| --output-dir | string | / | Path to directory which should be used to store files (default ".") |
| --partial-name-match | boolean | / | Get all files with a partial name match |
| --partial-name-match | boolean | false | Get all files with a partial name match |

#### Create files
Use the **create** command with the **--file** flag to upload one or more files
Expand All @@ -270,7 +267,52 @@ trickest files delete --file delete_me.txt
| --file | string | / | File or files (comma-separated) |


## Tools command
Manage [private tools](https://trickest.com/docs/tutorials/private-tools/private-tools-library/)

⚒️ Learn how to add your first tool integration [here](https://trickest.com/docs/tutorials/private-tools/dockerfile-and-trickest-yaml/).

#### Create a new private tool integration
```
trickest tools create --file tool.yaml
```

| Flag | Type | Default | Description |
|----------------------|--------|----------|---------------------------------------------------------------------|
| --file | string | / | YAML file for tool definition |

#### Update a private tool integration
```
trickest tools update --file tool.yaml
```

| Flag | Type | Default | Description |
|----------------------|--------|----------|---------------------------------------------------------------------|
| --file | string | / | YAML file for tool definition |

#### List private tool integrations
```
trickest tools list
```

| Flag | Type | Default | Description |
|----------------------|---------|-------------|---------------------------------------------------------------------|
| --json | boolean | false | Display output in JSON format |

#### Delete a private tool integration
```
trickest tools delete --name "my-tool"
```

| Flag | Type | Default | Description |
|----------------------|--------|----------|---------------------------------------------------------------------|
| --id | string | / | ID of the tool to delete |
| --name | string | / | Name of the tool 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).

You can also create an [Issue](https://github.com/trickest/trickest-cli/issues/new/choose) in the Github repository.

[<img src="./banner.png" />](https://trickest.io/auth/register)

29 changes: 29 additions & 0 deletions cmd/library/library.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package library

import (
"encoding/json"
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/trickest/trickest-cli/types"
"github.com/xlab/treeprint"
)

var (
Expand All @@ -27,3 +33,26 @@ func init() {
command.Root().HelpFunc()(command, strings)
})
}

func PrintTools(tools []types.Tool, jsonOutput bool) {
var output string
if jsonOutput {
data, err := json.Marshal(tools)
if err != nil {
fmt.Println("Error marshalling project data")
return
}
output = string(data)
} else {
tree := treeprint.New()
tree.SetValue("Tools")
for _, tool := range tools {
branch := tree.AddBranch(tool.Name + " [" + strings.TrimPrefix(tool.SourceURL, "https://") + "]")
branch.AddNode("\U0001f4cb \033[3m" + tool.Description + "\033[0m") //📋
}

output = tree.String()
}

fmt.Println(output)
}
29 changes: 1 addition & 28 deletions cmd/library/libraryListTools.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package library

import (
"encoding/json"
"fmt"
"math"
"strings"

"github.com/spf13/cobra"
"github.com/trickest/trickest-cli/cmd/list"
"github.com/trickest/trickest-cli/types"
"github.com/xlab/treeprint"
)

// libraryListToolsCmd represents the libraryListTools command
Expand All @@ -20,7 +16,7 @@ var libraryListToolsCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
tools := list.GetTools(math.MaxInt, "", "")
if len(tools) > 0 {
printTools(tools, jsonOutput)
PrintTools(tools, jsonOutput)
} else {
fmt.Println("Couldn't find any tool in the library!")
}
Expand All @@ -31,26 +27,3 @@ func init() {
libraryListCmd.AddCommand(libraryListToolsCmd)
libraryListToolsCmd.Flags().BoolVar(&jsonOutput, "json", false, "Display output in JSON format")
}

func printTools(tools []types.Tool, jsonOutput bool) {
var output string
if jsonOutput {
data, err := json.Marshal(tools)
if err != nil {
fmt.Println("Error marshalling project data")
return
}
output = string(data)
} else {
tree := treeprint.New()
tree.SetValue("Tools")
for _, tool := range tools {
branch := tree.AddBranch(tool.Name + " [" + strings.TrimPrefix(tool.SourceURL, "https://") + "]")
branch.AddNode("\U0001f4cb \033[3m" + tool.Description + "\033[0m") //📋
}

output = tree.String()
}

fmt.Println(output)
}
2 changes: 1 addition & 1 deletion cmd/library/librarySearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var librarySearchCmd = &cobra.Command{
fmt.Println(output)
} else {
if len(tools) > 0 {
printTools(tools, jsonOutput)
PrintTools(tools, jsonOutput)
} else {
fmt.Println("Couldn't find any tool in the library that matches the search!")
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/tools"
"github.com/trickest/trickest-cli/util"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -52,6 +53,7 @@ func init() {
RootCmd.AddCommand(execute.ExecuteCmd)
RootCmd.AddCommand(get.GetCmd)
RootCmd.AddCommand(files.FilesCmd)
RootCmd.AddCommand(tools.ToolsCmd)
// RootCmd.AddCommand(export.ExportCmd)
}

Expand Down
154 changes: 154 additions & 0 deletions cmd/tools/tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package tools

import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"

"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 toolOutputTypes = map[string]string{
"file": "2",
"folder": "3",
}

var ToolsCmd = &cobra.Command{
Use: "tools",
Short: "Manage private tools",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}

func init() {
ToolsCmd.SetHelpFunc(func(command *cobra.Command, strings []string) {
_ = ToolsCmd.Flags().MarkHidden("workflow")
_ = ToolsCmd.Flags().MarkHidden("project")
_ = ToolsCmd.Flags().MarkHidden("space")
_ = ToolsCmd.Flags().MarkHidden("url")

command.Root().HelpFunc()(command, strings)
})
}

func ListPrivateTools(name string) ([]types.Tool, error) {
endpoint := "library/tool/?public=False"
endpoint += fmt.Sprintf("&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 tools types.Tools
err := json.Unmarshal(resp.Body(), &tools)
if err != nil {
return nil, fmt.Errorf("couldn't parse API response: %s", err)
}

return tools.Results, nil
}

func getToolIDByName(name string) (uuid.UUID, error) {
tools, err := ListPrivateTools(name)
if err != nil {
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)
}

return tools[0].ID, nil
}

func createToolImportRequestFromYAML(fileName string) (types.ToolImportRequest, error) {
data, err := os.ReadFile(fileName)
if err != nil {
err = fmt.Errorf("couldn't read %s: %w", fileName, err)
return types.ToolImportRequest{}, err
}

var toolImportRequest types.ToolImportRequest
err = yaml.Unmarshal(data, &toolImportRequest)
if err != nil {
err = fmt.Errorf("couldn't parse %s: %w", fileName, err)
return types.ToolImportRequest{}, err
}

categoryID, err := util.GetCategoryIDByName(toolImportRequest.Category)
if err != nil {
err = fmt.Errorf("couldn't use the category '%s': %w", toolImportRequest.Category, err)
return types.ToolImportRequest{}, err
}

toolImportRequest.CategoryID = categoryID
toolImportRequest.VaultInfo = util.GetVault()
toolImportRequest.OutputType = toolOutputTypes[toolImportRequest.OutputType]
for name := range toolImportRequest.Inputs {
if input, ok := toolImportRequest.Inputs[name]; ok {
input.Type = strings.ToUpper(toolImportRequest.Inputs[name].Type)
toolImportRequest.Inputs[name] = input
}
}

return toolImportRequest, nil
}

func importTool(fileName string, isUpdate bool) (string, uuid.UUID, error) {
toolImportRequest, err := createToolImportRequestFromYAML(fileName)
if err != nil {
return "", uuid.Nil, err
}

toolJSON, err := json.Marshal(toolImportRequest)
if err != nil {
return "", uuid.Nil, fmt.Errorf("couldn't encode %s: %w", fileName, err)
}

var resp *request.Response
if isUpdate {
toolName := toolImportRequest.Name
toolID, err := getToolIDByName(toolName)
if err != nil {
return "", uuid.Nil, fmt.Errorf("couldn't import '%s': %w", toolName, err)
}
resp = request.Trickest.Patch().Body(toolJSON).DoF("library/tool/%s/", toolID.String())
} else {
resp = request.Trickest.Post().Body(toolJSON).Do("library/tool/")
}

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 importedTool types.Tool
err = json.Unmarshal(resp.Body(), &importedTool)
if err != nil {
return "", uuid.Nil, fmt.Errorf("couldn't import %s: %w", fileName, err)
}

return importedTool.Name, importedTool.ID, nil
}
Loading