Skip to content

Commit 3fc36d6

Browse files
committed
feat: add raw query and custom command functionality
1 parent 8cb41ab commit 3fc36d6

8 files changed

Lines changed: 459 additions & 2 deletions

File tree

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ build-linux: test
3939
build-darwin: test
4040
GO111MODULE=on CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOCMD) build -ldflags '${LDFLAGS} -X "${PKG}/cmd.lagoonCLIBuildGoVersion=${GO_VER}"' -o builds/lagoon-cli-${VERSION}-darwin-amd64 -v
4141

42-
docs: test build
43-
GO111MODULE=on $(GOCMD) run main.go --docs
42+
docs: test
43+
LAGOON_GEN_DOCS=true GO111MODULE=on $(GOCMD) run main.go --docs
4444

4545
tidy:
4646
GO111MODULE=on $(GOCMD) mod tidy

cmd/raw.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
9+
"github.com/spf13/cobra"
10+
"github.com/uselagoon/lagoon-cli/pkg/output"
11+
lclient "github.com/uselagoon/machinery/api/lagoon/client"
12+
"gopkg.in/yaml.v3"
13+
)
14+
15+
// CustomCommand is the custom command data structure, this is what can be used to define custom commands
16+
type CustomCommand struct {
17+
Name string `yaml:"name"`
18+
Description string `yaml:"description"`
19+
Query string `yaml:"query"`
20+
Flags []struct {
21+
Name string `yaml:"name"`
22+
Description string `yaml:"description"`
23+
Variable string `yaml:"variable"`
24+
Type string `yaml:"type"`
25+
Required bool `yaml:"required"`
26+
Default *interface{} `yaml:"default,omitempty"`
27+
} `yaml:"flags"`
28+
}
29+
30+
var emptyCmd = cobra.Command{
31+
Use: "none",
32+
Aliases: []string{""},
33+
Short: "none",
34+
Hidden: true,
35+
PreRunE: func(_ *cobra.Command, _ []string) error {
36+
return validateTokenE(lagoonCLIConfig.Current)
37+
},
38+
RunE: func(cmd *cobra.Command, args []string) error {
39+
return nil
40+
},
41+
}
42+
43+
var rawCmd = &cobra.Command{
44+
Use: "raw",
45+
Aliases: []string{"r"},
46+
Short: "Run a custom query or mutation",
47+
Long: `Run a custom query or mutation.
48+
The output of this command will be the JSON response from the API`,
49+
PreRunE: func(_ *cobra.Command, _ []string) error {
50+
return validateTokenE(cmdLagoon)
51+
},
52+
RunE: func(cmd *cobra.Command, args []string) error {
53+
debug, err := cmd.Flags().GetBool("debug")
54+
if err != nil {
55+
return err
56+
}
57+
raw, err := cmd.Flags().GetString("raw")
58+
if err != nil {
59+
return err
60+
}
61+
if err := requiredInputCheck("Raw query or mutation", raw); err != nil {
62+
return err
63+
}
64+
current := lagoonCLIConfig.Current
65+
token := lagoonCLIConfig.Lagoons[current].Token
66+
lc := lclient.New(
67+
lagoonCLIConfig.Lagoons[current].GraphQL,
68+
lagoonCLIVersion,
69+
&token,
70+
debug)
71+
if err != nil {
72+
return err
73+
}
74+
rawResp, err := lc.ProcessRaw(context.TODO(), raw, nil)
75+
if err != nil {
76+
return err
77+
}
78+
r, err := json.Marshal(rawResp)
79+
if err != nil {
80+
return err
81+
}
82+
fmt.Println(string(r))
83+
return nil
84+
},
85+
}
86+
87+
var customCmd = &cobra.Command{
88+
Use: "custom",
89+
Aliases: []string{"cus", "cust"},
90+
Short: "Run a custom command",
91+
Long: `Run a custom command.
92+
This command alone does nothing, but you can create custom commands and put them into the custom commands directory,
93+
these commands will then be available to use.
94+
The directory for custom commands is ${HOME}/.lagoon-cli/commands.`,
95+
RunE: func(cmd *cobra.Command, args []string) error {
96+
// just return the help menu for this command as if it is just a normal parent with children commands
97+
cmd.Help()
98+
return nil
99+
},
100+
}
101+
102+
func ReadCustomCommands() ([]*cobra.Command, error) {
103+
userPath, err := os.UserHomeDir()
104+
if err != nil {
105+
return nil, fmt.Errorf("couldn't get $HOME: %v", err)
106+
}
107+
customCommandsFilePath := fmt.Sprintf("%s/%s", userPath, commandsFilePath)
108+
if _, err := os.Stat(customCommandsFilePath); os.IsNotExist(err) {
109+
err := os.MkdirAll(customCommandsFilePath, 0700)
110+
if err != nil {
111+
return nil, fmt.Errorf("couldn't create command directory %s: %v", customCommandsFilePath, err)
112+
}
113+
}
114+
files, err := os.ReadDir(customCommandsFilePath)
115+
if err != nil {
116+
return nil, fmt.Errorf("couldn't open command directory %s: %v", customCommandsFilePath, err)
117+
}
118+
var cmds []*cobra.Command
119+
if len(files) != 0 {
120+
for _, file := range files {
121+
if !file.IsDir() {
122+
data, err := os.ReadFile(customCommandsFilePath + "/" + file.Name())
123+
if err != nil {
124+
return nil, err
125+
}
126+
raw := CustomCommand{}
127+
err = yaml.Unmarshal(data, &raw)
128+
if err != nil {
129+
return nil, fmt.Errorf("unable to unmarshal custom command '%s', yaml is likely invalid: %v", file.Name(), err)
130+
}
131+
cCmd := cobra.Command{
132+
Use: raw.Name,
133+
Aliases: []string{""},
134+
Short: raw.Description,
135+
PreRunE: func(_ *cobra.Command, _ []string) error {
136+
return validateTokenE(lagoonCLIConfig.Current)
137+
},
138+
RunE: func(cmd *cobra.Command, args []string) error {
139+
debug, err := cmd.Flags().GetBool("debug")
140+
if err != nil {
141+
return err
142+
}
143+
144+
variables := make(map[string]interface{})
145+
var value interface{}
146+
// handling reading the custom flags
147+
for _, flag := range raw.Flags {
148+
switch flag.Type {
149+
case "Int":
150+
value, err = cmd.Flags().GetInt(flag.Name)
151+
if err != nil {
152+
return err
153+
}
154+
if flag.Required {
155+
if err := requiredInputCheck(flag.Name, fmt.Sprintf("%d", value.(int))); err != nil {
156+
return err
157+
}
158+
}
159+
case "String":
160+
value, err = cmd.Flags().GetString(flag.Name)
161+
if err != nil {
162+
return err
163+
}
164+
if flag.Required {
165+
if err := requiredInputCheck(flag.Name, value.(string)); err != nil {
166+
return err
167+
}
168+
}
169+
case "Boolean":
170+
value, err = cmd.Flags().GetBool(flag.Name)
171+
if err != nil {
172+
return err
173+
}
174+
}
175+
variables[flag.Variable] = value
176+
}
177+
178+
current := lagoonCLIConfig.Current
179+
token := lagoonCLIConfig.Lagoons[current].Token
180+
lc := lclient.New(
181+
lagoonCLIConfig.Lagoons[current].GraphQL,
182+
lagoonCLIVersion,
183+
&token,
184+
debug)
185+
if err != nil {
186+
return err
187+
}
188+
189+
rawResp, err := lc.ProcessRaw(context.TODO(), raw.Query, variables)
190+
if err != nil {
191+
return err
192+
}
193+
r, err := json.Marshal(rawResp)
194+
if err != nil {
195+
return err
196+
}
197+
fmt.Println(string(r))
198+
return nil
199+
},
200+
}
201+
// add custom flags to the command
202+
for _, flag := range raw.Flags {
203+
switch flag.Type {
204+
case "Int":
205+
if flag.Default != nil {
206+
cCmd.Flags().Int(flag.Name, (*flag.Default).(int), flag.Description)
207+
} else {
208+
cCmd.Flags().Int(flag.Name, 0, flag.Description)
209+
}
210+
case "String":
211+
if flag.Default != nil {
212+
cCmd.Flags().String(flag.Name, (*flag.Default).(string), flag.Description)
213+
} else {
214+
cCmd.Flags().String(flag.Name, "", flag.Description)
215+
}
216+
case "Boolean":
217+
if flag.Default != nil {
218+
cCmd.Flags().Bool(flag.Name, (*flag.Default).(bool), flag.Description)
219+
} else {
220+
cCmd.Flags().Bool(flag.Name, false, flag.Description)
221+
}
222+
}
223+
}
224+
cmds = append(cmds, &cCmd)
225+
}
226+
}
227+
} else {
228+
cmds = append(cmds,
229+
// create a hidden command that does nothing so help and docs can be generated for the custom command
230+
&emptyCmd)
231+
}
232+
return cmds, nil
233+
}
234+
235+
func init() {
236+
if _, ok := os.LookupEnv("LAGOON_GEN_DOCS"); ok {
237+
// this is an override for when the docs are generated
238+
// so that it doesn't include any custom commands
239+
customCmd.AddCommand(&emptyCmd)
240+
} else {
241+
// read any custom commands
242+
cmds, err := ReadCustomCommands()
243+
if err != nil {
244+
output.RenderError(err.Error(), outputOptions)
245+
os.Exit(1)
246+
}
247+
for _, c := range cmds {
248+
customCmd.AddCommand(c)
249+
}
250+
}
251+
rawCmd.Flags().String("raw", "", "The raw query or mutation to run")
252+
}

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var configExtension = ".yml"
3838
var createConfig bool
3939
var userPath string
4040
var configFilePath string
41+
var commandsFilePath = ".lagoon-cli/commands"
4142
var updateDocURL = "https://uselagoon.github.io/lagoon-cli"
4243

4344
var skipUpdateCheck bool
@@ -195,6 +196,8 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e
195196
rootCmd.AddCommand(exportCmd)
196197
rootCmd.AddCommand(whoamiCmd)
197198
rootCmd.AddCommand(uploadCmd)
199+
rootCmd.AddCommand(rawCmd)
200+
rootCmd.AddCommand(customCmd)
198201
}
199202

200203
// version/build information command

docs/commands/lagoon.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ lagoon [flags]
3434
* [lagoon add](lagoon_add.md) - Add a project, or add notifications and variables to projects or environments
3535
* [lagoon completion](lagoon_completion.md) - Generate the autocompletion script for the specified shell
3636
* [lagoon config](lagoon_config.md) - Configure Lagoon CLI
37+
* [lagoon custom](lagoon_custom.md) - Run a custom command
3738
* [lagoon delete](lagoon_delete.md) - Delete a project, or delete notifications and variables from projects or environments
3839
* [lagoon deploy](lagoon_deploy.md) - Actions for deploying or promoting branches or environments in lagoon
3940
* [lagoon export](lagoon_export.md) - Export lagoon output to yaml
@@ -42,6 +43,7 @@ lagoon [flags]
4243
* [lagoon kibana](lagoon_kibana.md) - Launch the kibana interface
4344
* [lagoon list](lagoon_list.md) - List projects, environments, deployments, variables or notifications
4445
* [lagoon login](lagoon_login.md) - Log into a Lagoon instance
46+
* [lagoon raw](lagoon_raw.md) - Run a custom query or mutation
4547
* [lagoon retrieve](lagoon_retrieve.md) - Trigger a retrieval operation on backups
4648
* [lagoon run](lagoon_run.md) - Run a task against an environment
4749
* [lagoon ssh](lagoon_ssh.md) - Display the SSH command to access a specific environment in a project

docs/commands/lagoon_custom.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## lagoon custom
2+
3+
Run a custom command
4+
5+
### Synopsis
6+
7+
Run a custom command.
8+
This command alone does nothing, but you can create custom commands and put them into the custom commands directory,
9+
these commands will then be available to use.
10+
The directory for custom commands is ${HOME}/.lagoon-cli/commands.
11+
12+
```
13+
lagoon custom [flags]
14+
```
15+
16+
### Options
17+
18+
```
19+
-h, --help help for custom
20+
```
21+
22+
### Options inherited from parent commands
23+
24+
```
25+
--config-file string Path to the config file to use (must be *.yml or *.yaml)
26+
--debug Enable debugging output (if supported)
27+
-e, --environment string Specify an environment to use
28+
--force Force yes on prompts (if supported)
29+
-l, --lagoon string The Lagoon instance to interact with
30+
--no-header No header on table (if supported)
31+
--output-csv Output as CSV (if supported)
32+
--output-json Output as JSON (if supported)
33+
--pretty Make JSON pretty (if supported)
34+
-p, --project string Specify a project to use
35+
--skip-update-check Skip checking for updates
36+
-i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication
37+
```
38+
39+
### SEE ALSO
40+
41+
* [lagoon](lagoon.md) - Command line integration for Lagoon
42+

docs/commands/lagoon_raw.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## lagoon raw
2+
3+
Run a custom query or mutation
4+
5+
### Synopsis
6+
7+
Run a custom query or mutation.
8+
The output of this command will be the JSON response from the API
9+
10+
```
11+
lagoon raw [flags]
12+
```
13+
14+
### Options
15+
16+
```
17+
-h, --help help for raw
18+
--raw string The raw query or mutation to run
19+
```
20+
21+
### Options inherited from parent commands
22+
23+
```
24+
--config-file string Path to the config file to use (must be *.yml or *.yaml)
25+
--debug Enable debugging output (if supported)
26+
-e, --environment string Specify an environment to use
27+
--force Force yes on prompts (if supported)
28+
-l, --lagoon string The Lagoon instance to interact with
29+
--no-header No header on table (if supported)
30+
--output-csv Output as CSV (if supported)
31+
--output-json Output as JSON (if supported)
32+
--pretty Make JSON pretty (if supported)
33+
-p, --project string Specify a project to use
34+
--skip-update-check Skip checking for updates
35+
-i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication
36+
```
37+
38+
### SEE ALSO
39+
40+
* [lagoon](lagoon.md) - Command line integration for Lagoon
41+

0 commit comments

Comments
 (0)