Skip to content

Commit

Permalink
feat: add raw query and custom command functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
shreddedbacon committed Jul 24, 2024
1 parent d37f1b2 commit e350aea
Show file tree
Hide file tree
Showing 8 changed files with 467 additions and 1 deletion.
152 changes: 151 additions & 1 deletion cmd/raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,35 @@ import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/uselagoon/lagoon-cli/internal/custom"
"github.com/uselagoon/lagoon-cli/pkg/output"
lclient "github.com/uselagoon/machinery/api/lagoon/client"
)

var emptyCmd = cobra.Command{
Use: "none",
Aliases: []string{""},
Short: "none",
Hidden: true,
PreRunE: func(_ *cobra.Command, _ []string) error {
return validateTokenE(lContext.Name)
},
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}

var rawCmd = &cobra.Command{
Use: "raw",
Aliases: []string{"r"},
Short: "Run a custom query or mutation",
Long: `Run a custom query or mutation.
The output of this command will be the JSON response from the API`,
PreRunE: func(_ *cobra.Command, _ []string) error {
return validateTokenE(cmdLagoon)
return validateTokenE(lContext.Name)
},
RunE: func(cmd *cobra.Command, args []string) error {
debug, err := cmd.Flags().GetBool("debug")
Expand Down Expand Up @@ -53,6 +69,140 @@ The output of this command will be the JSON response from the API`,
},
}

var customCmd = &cobra.Command{
Use: "custom",
Aliases: []string{"cus", "cust"},
Short: "Run a custom command",
Long: `Run a custom command.
This command alone does nothing, but you can create custom commands and put them into the custom commands directory,
these commands will then be available to use.
The directory for custom commands uses ${XDG_DATA_HOME}/lagoon-commands.
If XDG_DATA_HOME is not defined, a directory will be created with the defaults, this command will output the location at the end.
`,
RunE: func(cmd *cobra.Command, args []string) error {
commandsDir, err := custom.GetCommandsLocation()
if err != nil {
return err
}
// just return the help menu for this command as if it is just a normal parent with children commands
cmd.Help()
fmt.Println("Save your command YAML files into the following directory")
fmt.Println(commandsDir)
return nil
},
}

func ConvertToCobra(raw custom.CustomCommand) *cobra.Command {
cCmd := &cobra.Command{
Use: raw.Name,
Short: raw.Description,
PreRunE: func(_ *cobra.Command, _ []string) error {
return validateTokenE(lContext.Name)
},
RunE: func(cmd *cobra.Command, args []string) error {
debug, err := cmd.Flags().GetBool("debug")
if err != nil {
return err
}

variables := make(map[string]interface{})
var value interface{}
// handling reading the custom flags
for _, flag := range raw.Flags {
switch flag.Type {
case "Int":
value, err = cmd.Flags().GetInt(flag.Name)
if err != nil {
return err
}
if flag.Required {
if err := requiredInputCheck(flag.Name, fmt.Sprintf("%d", value.(int))); err != nil {
return err
}
}
case "String":
value, err = cmd.Flags().GetString(flag.Name)
if err != nil {
return err
}
if flag.Required {
if err := requiredInputCheck(flag.Name, value.(string)); err != nil {
return err
}
}
case "Boolean":
value, err = cmd.Flags().GetBool(flag.Name)
if err != nil {
return err
}
}
variables[flag.Variable] = value
}

utoken := lUser.UserConfig.Grant.AccessToken
lc := lclient.New(
fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname),
lagoonCLIVersion,
lContext.ContextConfig.Version,
&utoken,
debug)
if err != nil {
return err
}

rawResp, err := lc.ProcessRaw(context.TODO(), raw.Query, variables)
if err != nil {
return err
}
r, err := json.Marshal(rawResp)
if err != nil {
return err
}
fmt.Println(string(r))
return nil
},
}
// add custom flags to the command
for _, flag := range raw.Flags {
switch flag.Type {
case "Int":
if flag.Default != nil {
cCmd.Flags().Int(flag.Name, (*flag.Default).(int), flag.Description)
} else {
cCmd.Flags().Int(flag.Name, 0, flag.Description)
}
case "String":
if flag.Default != nil {
cCmd.Flags().String(flag.Name, (*flag.Default).(string), flag.Description)
} else {
cCmd.Flags().String(flag.Name, "", flag.Description)
}
case "Boolean":
if flag.Default != nil {
cCmd.Flags().Bool(flag.Name, (*flag.Default).(bool), flag.Description)
} else {
cCmd.Flags().Bool(flag.Name, false, flag.Description)
}
}
}
return cCmd
}

func init() {
if _, ok := os.LookupEnv("LAGOON_GEN_DOCS"); ok {
// this is an override for when the docs are generated
// so that it doesn't include any custom commands
customCmd.AddCommand(&emptyCmd)
} else {
// read any custom commands
cmds2, err := custom.LoadCommands(true)
if err != nil {
output.RenderError(err.Error(), outputOptions)
os.Exit(1)
}
for _, cm := range cmds2.Commands {
customCmd.AddCommand(ConvertToCobra(cm))
}
}
rawCmd.Flags().String("raw", "", "The raw query or mutation to run")
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e
rootCmd.AddCommand(configCmd)
rootCmd.AddCommand(resetPasswordCmd)
rootCmd.AddCommand(logsCmd)
rootCmd.AddCommand(customCmd)
}

// version/build information command
Expand Down
1 change: 1 addition & 0 deletions docs/commands/lagoon.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ lagoon [flags]
* [lagoon add](lagoon_add.md) - Add a project, or add notifications and variables to projects or environments
* [lagoon completion](lagoon_completion.md) - Generate the autocompletion script for the specified shell
* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon
* [lagoon custom](lagoon_custom.md) - Run a custom command
* [lagoon delete](lagoon_delete.md) - Delete a project, or delete notifications and variables from projects or environments
* [lagoon deploy](lagoon_deploy.md) - Actions for deploying or promoting branches or environments in lagoon
* [lagoon export](lagoon_export.md) - Export lagoon output to yaml
Expand Down
47 changes: 47 additions & 0 deletions docs/commands/lagoon_custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## lagoon custom

Run a custom command

### Synopsis

Run a custom command.
This command alone does nothing, but you can create custom commands and put them into the custom commands directory,
these commands will then be available to use.
The directory for custom commands uses ${XDG_DATA_HOME}/lagoon-commands.
If XDG_DATA_HOME is not defined, a directory will be created with the defaults, this command will output the location at the end.


```
lagoon custom [flags]
```

### Options

```
-h, --help help for custom
```

### 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
--ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent.
This will override any public key identities defined in configuration
-v, --verbose Enable verbose output to stderr (if supported)
```

### SEE ALSO

* [lagoon](lagoon.md) - Command line integration for Lagoon

116 changes: 116 additions & 0 deletions docs/customcommands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Introduction

Lagoon allows users to create simple custom commands that can execute raw graphql queries or mutations. The response of these commands will be the JSON response from the API, so tools like `jq` can be used to parse the response.

These commands are meant for simple tasks, and may not perform complex things very well. In some cases, the defaults of a flag may not work as you intend them to.

> **_NOTE:_** as always, be careful with creating your own commands, especially mutations, as you must be 100% aware of the implications.
## Location

Custom commands must be saved to `${HOME}/.lagoon-cli/commands/${COMMAND_NAME}.yml`

## Layout of a command file

An example of the command file structure is as follows
```yaml
name: project-by-name
description: Query a project by name
query: |
query projectByName($name: String!) {
projectByName(name: $name) {
id
name
organization
openshift{
name
}
environments{
name
openshift{
name
}
}
}
}
flags:
- name: name
description: Project name to check
variable: name
type: String
required: true
```
* `name` is the name of the command that the user must enter, this should be unique
* `description` is some helpful information about this command
* `query` is the query or mutation that is run
* `flags` allows you to define your own flags
* `name` is the name of the flag, eg `--name`
* `description` is some helpful information about the flag
* `variable` is the name of the variable that will be passed to the graphql query of the same name
* `type` is the type, currently only `String`, `Int`, `Boolean` are supported
* `required` is if this flag is required or not
* `default` is the default value of the flag if defined
* `String` defaults to ""
* `Int` defaults to 0
* `Boolean` defaults to false.

# Usage

Once a command file has been created, they will appear as `Available Commands` of the top level `custom` command, similarly to below

```
$ lagoon custom
Usage:
lagoon custom [flags]
lagoon custom [command]

Aliases:
custom, cus, cust

Available Commands:
project-by-name Query a project by name

```
You can then call this command like so, and see the output of the command is the API JSON response
```
$ lagoon custom project-by-name --name lagoon-demo-org | jq
{
"projectByName": {
"environments": [
{
"name": "development",
"openshift": {
"name": "ui-kubernetes-2"
}
},
{
"name": "main",
"openshift": {
"name": "ui-kubernetes-2"
}
},
{
"name": "pr-15",
"openshift": {
"name": "ui-kubernetes-2"
}
},
{
"name": "staging",
"openshift": {
"name": "ui-kubernetes-2"
}
}
],
"id": 180,
"name": "lagoon-demo-org",
"openshift": {
"name": "ui-kubernetes-2"
},
"organization": 1
}
}

```
Loading

0 comments on commit e350aea

Please sign in to comment.