Skip to content

Commit

Permalink
add support for config contexts: move viper access to new config pack… (
Browse files Browse the repository at this point in the history
#140)

* add support for config contexts
  • Loading branch information
0x4c6565 authored Jun 20, 2022
1 parent ac431da commit c84ddc4
Show file tree
Hide file tree
Showing 21 changed files with 901 additions and 287 deletions.
1 change: 0 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ project_name: ukfast
before:
hooks:
- go mod download
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
Expand Down
43 changes: 28 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,35 +63,48 @@ Both of these methods are explained below.
The configuration file is read from
`$HOME/.ukfast{.extension}` by default (extension being one of the `viper` supported formats such as `yml`, `yaml`, `json`, `toml` etc.). This path can be overridden with the `--config` flag.

The API key can be written to the configuration file:
```
> ukfast config set --api_key="123456789abcdefghijklmnopqrstuvw"
```
Check that this has been written:
```
> cat ~/.ukfast.yml
api_key: 123456789abcdefghijklmnopqrstuvw
```
Values defined in the configuration file take precedence over environment variables.

#### Required

* `api_key`: API key for interacting with UKFast APIs

#### Debug
### Schema

* `api_key`: (String) *Required* API key for authenticating with API
* `api_timeout_seconds`: (int) HTTP timeout for API requests. Default: `90`
* `api_uri`: (string) API URI. Default: `api.ukfast.io`
* `api_insecure`: (bool) Specifies to ignore API certificate validation checks
* `api_debug`: (bool) Specifies for debug messages to be output to stderr
* `api_pagination_perpage` (int) Specifies the per-page for paginated requests

### Contexts

Contexts can be defined in the config file to allow for different sets of configuration to be defined:

```yaml
contexts:
testcontext1:
api_key: mykey1
testcontext2:
api_key: mykey2
current_context: testcontext1
```
The current context can also be overridden with the `--context` flag

### Commands

The configuration file can be manipulated using the `config` subcommand, for example:


```
> ukfast config context update --current --api-key test1
> ukfast config context update someothercontext --api-key test1
> ukfast config context switch someothercontext
```

### Environment variables

Environment variables can be used to configure/manipulate the CLI. These variables match the naming of directives in the configuration file
defined above, however are uppercased and prefixed with `UKF`, such as `UKF_API_KEY`


## Output Formatting

The output of all commands is determined by a single global flag `--output` / `-o`.
Expand Down
85 changes: 0 additions & 85 deletions cmd/config.go

This file was deleted.

18 changes: 18 additions & 0 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package config

import (
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

func ConfigRootCmd(fs afero.Fs) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "sub-commands relating to CLI config",
}

// Child root commands
cmd.AddCommand(configContextRootCmd(fs))

return cmd
}
168 changes: 168 additions & 0 deletions cmd/config/config_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package config

import (
"errors"
"fmt"
"strconv"

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/ukfast/cli/internal/pkg/config"
"github.com/ukfast/cli/internal/pkg/output"
)

func configContextRootCmd(fs afero.Fs) *cobra.Command {
cmd := &cobra.Command{
Use: "context",
Short: "sub-commands relating to CLI config",
}

// Child commands
cmd.AddCommand(configContextUpdateCmd(fs))
cmd.AddCommand(configContextListCmd())
cmd.AddCommand(configContextSwitchCmd(fs))

return cmd
}

func configContextUpdateCmd(fs afero.Fs) *cobra.Command {
cmd := &cobra.Command{
Use: "update",
Short: "Updates context configuration",
Long: "This command updates context configuration",
Example: "ukfast config context update mycontext --api-key \"secretkey\"",
RunE: func(cmd *cobra.Command, args []string) error {
return configContextUpdate(fs, cmd, args)
},
}

// Setup flags
cmd.Flags().Bool("current", false, "Specifies that current context should be updated")
cmd.Flags().String("api-key", "", "Specifies API key")
cmd.Flags().Int("api-timeout-seconds", 0, "Specifies API timeout in seconds")
cmd.Flags().String("api-uri", "", "Specifies API URI")
cmd.Flags().Bool("api-insecure", false, "Specifies API TLS validation should be disabled")
cmd.Flags().Bool("api-debug", false, "Specifies API debug logging should be enabled")
cmd.Flags().Int("api-pagination-perpage", 0, "Specifies how many items should be retrieved per-page for paginated API requests")
cmd.Flags().Int("command-wait-timeout-seconds", 0, "Specifies how long commands supporting 'wait' parameter should wait")
cmd.Flags().Int("command-wait-sleep-seconds", 0, "Specifies how often commands supporting 'wait' parameter should poll")

return cmd
}

func configContextUpdate(fs afero.Fs, cmd *cobra.Command, args []string) error {
updated := false

updateCurrentContext, _ := cmd.Flags().GetBool("current")

set := func(name string, flagName string, value interface{}) {
if cmd.Flags().Changed(flagName) {
if updateCurrentContext {
err := config.SetCurrentContext(name, value)
if err != nil {
output.Fatalf("failed to update current context: %s", err)
}
} else {
for _, context := range args {
config.Set(context, name, value)
}
}
updated = true
}
}

apiKey, _ := cmd.Flags().GetString("api-key")
set("api_key", "api-key", apiKey)
apiTimeoutSeconds, _ := cmd.Flags().GetInt("api-timeout-seconds")
set("api_timeout_seconds", "api-timeout-seconds", apiTimeoutSeconds)
apiURI, _ := cmd.Flags().GetString("api-uri")
set("api_uri", "api-uri", apiURI)
apiInsecure, _ := cmd.Flags().GetBool("api-insecure")
set("api_insecure", "api-insecure", apiInsecure)
apiDebug, _ := cmd.Flags().GetBool("api-debug")
set("api_debug", "api-debug", apiDebug)
apiPaginationPerPage, _ := cmd.Flags().GetInt("api-pagination-perpage")
set("api_pagination_perpage", "api-pagination-perpage", apiPaginationPerPage)
commandWaitTimeoutSeconds, _ := cmd.Flags().GetInt("command-wait-timeout-seconds")
set("command_wait_timeout_seconds", "command-wait-timeout-seconds", commandWaitTimeoutSeconds)
commandWaitSleepSeconds, _ := cmd.Flags().GetInt("command-wait-sleep-seconds")
set("command_wait_sleep_seconds", "command-wait-sleep-seconds", commandWaitSleepSeconds)

if updated {
config.SetFs(fs)
return config.Save()
}

return nil
}

func configContextListCmd() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "Lists contexts",
Long: "This command lists contexts",
Example: "ukfast config context list",
RunE: func(cmd *cobra.Command, args []string) error {
return configContextList(cmd)
},
}
}

func configContextList(cmd *cobra.Command) error {
contextNames := config.GetContextNames()
currentContextName := config.GetCurrentContextName()

var data []interface{}
var fields []*output.OrderedFields
for _, contextName := range contextNames {
activeContext := contextName == currentContextName
data = append(data, struct {
Name string `json:"name"`
Active bool `json:"active"`
}{
Name: contextName,
Active: activeContext,
})
field := output.NewOrderedFields()
field.Set("name", output.NewFieldValue(contextName, true))
field.Set("active", output.NewFieldValue(strconv.FormatBool(activeContext), true))
fields = append(fields, field)
}

return output.CommandOutput(cmd, output.NewGenericOutputHandlerDataProvider(
output.WithData(data),
output.WithFieldDataFunc(func() ([]*output.OrderedFields, error) {
return fields, nil
}),
))
}

func configContextSwitchCmd(fs afero.Fs) *cobra.Command {
return &cobra.Command{
Use: "switch",
Short: "Switches current context",
Long: "This command switches the current context",
Example: "ukfast config context switch mycontext",
Aliases: []string{"use"},
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("Missing context")
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return configContextSwitch(fs, cmd, args)
},
}
}

func configContextSwitch(fs afero.Fs, cmd *cobra.Command, args []string) error {
err := config.SwitchCurrentContext(args[0])
if err != nil {
return fmt.Errorf("failed to switch context: %s", err)
}

config.SetFs(fs)
return config.Save()
}
Loading

0 comments on commit c84ddc4

Please sign in to comment.