https://github.com/fredbi/go-cli
A few utilities to build CLI on top of cobra and viper
https://github.com/fredbi/go-cli
cli configuration-management golang
Last synced: about 1 month ago
JSON representation
A few utilities to build CLI on top of cobra and viper
- Host: GitHub
- URL: https://github.com/fredbi/go-cli
- Owner: fredbi
- License: apache-2.0
- Created: 2022-11-09T19:09:23.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2023-11-17T11:30:41.000Z (over 2 years ago)
- Last Synced: 2025-11-23T22:08:41.549Z (8 months ago)
- Topics: cli, configuration-management, golang
- Language: Go
- Homepage:
- Size: 153 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README


[](https://coveralls.io/github/fredbi/go-cli)

[](https://goreportcard.com/report/github.com/fredbi/go-cli)

[](https://pkg.go.dev/github.com/fredbi/go-cli)
[](https://raw.githubusercontent.com/fredbi/go-cli/master/LICENSE.md)
# go-cli
This repo exposes a few utilities to
(i) [build command-line utilities](#CLI) with
(ii) [flexible configurations](#Configuration)
on top of 3 great libraries:
[`github.com/spf13/cobra`](https://github.com/spf13/cobra),
[`github.com/spf13/viper`](https://github.com/spf13/viper), and
[`github.com/spf13/pflag`](https://github.com/spf13/pflag).
**TL,DR**: this is not yet another CLI-building library, but rather a mere wrapper on top of `cobra`
to use that great lib with a functional style.
## CLI
### Example for CLI
Sample CLI-building code. This example is taken from [one of the testable examples](cli/example_test.go).
Notice our main objectives here:
* no globals
* inline flag registration & binding
* access to settings using viper only
```go
package main
import (
"fmt"
"github.com/fredbi/go-cli/cli"
"github.com/spf13/cobra"
)
const (
// viper config keys
keyLog = "app.log.level"
keyDry = "run.dryRun"
)
func main() {
// no global vars, no init() ...
if err := RootCmd().Execute(); err != nil {
cli.Die("executing: %v", err)
}
}
// RootCmd builds a runnable root command
func RootCmd() *cli.Command {
return cli.NewCommand(
// your usual cobra command, wrapped as a function
&cobra.Command{
Use: "example",
Short: "examplifies a cobra command",
Long: "...",
RunE: rootRunFunc,
},
// flag bindings
// {flag name}, {the flag type is inferred from the default value}, {flag help description}
cli.WithFlag("dry-run", false, "Dry run",
cli.BindFlagToConfig(keyDry), // flag bindings to a viper config
),
// a flag inherited by subcommands
cli.WithPersistentFlag("log-level", "info", "Controls logging verbosity",
cli.BindFlagToConfig(keyLog),
),
// apply viper config to the command tree
// command binding to a viper config -> config will be available from context
cli.WithConfig(cli.Config()),
)
}
// rootRunFunc runs the root command
func rootRunFunc(c *cobra.Command, _ []string) error {
// retrieve injected dependencies, create new empty viper registry if unresolved
cfg := injectable.ConfigFromContext(c.Context(), viper.New)
fmt.Println(
"example called\n",
fmt.Sprintf("dry-run: %t\n", cfg.GetBool(keyDry)),
fmt.Sprintf("log level config: %s\n", cfg.GetString(keyLog)),
)
return nil
}
```
### Goals
The `cli` packages proposes an opinionated approach to building command-line binaries on top of `github.com/spf13/cobra`.
> There are a few great existing libraries around to build a CLI.
> I believe that `cobra` stands out as the richest and most flexible,
> as CLIs are entirely built programmatically.
`cobra` is great, but building CLIs again and again, I came to identify a few repetitive boiler-plate patterns.
So this module reflects my opinions about how to build more elegant CLIs, wich abide by [12-factor](https://12factor.net)
out-of-the-box, with more expressive code and less low-level tinkering.
Feedback is always welcome, as opinions may evolve over time...
Feel free to post issues to leave your comments and/or proposals.
[More detailed design goals](docs/goals.md#CLI)
## Configuration
The `config` package proposes an opinionated approach to dealing with config files on top of `github.com/spf13/viper`.
It exposes configuration loaders which know about the deployment context
(e.g a deployment environment such as `dev`, `production`) and secrets.
Although developped primarily to serve a CLI, this package may be used independently.
### Example: loading a config
Other examples are available [here](config/examples_test.go).
```go
import (
"fmt"
"log"
"github.com/fredbi/go-cli/config"
)
...
// load and merge configuration files for environment "dev"
cfg, err := config.Load("dev", config.WithMute(true))
if err != nil {
log.Fatalf("loading config: %w", err)
return
}
```
### Goals
This describes my approach to configuration. We want to:
1. retrieve a config organized as a hierarchy of settings, e.g. a YAML document
2. merge configuration files with environment-specific settings
3. merge configuration files with secrets, usually these are environment-specific
4. clearly isolate and merge default settings
5. applications to be able to consume the settings from a single viper configuration registry
In addition,
* we want the hierarchy to be agnostic to the environment context
* most of the time, we don't want env-specific sections to propagate to the app level
(e.g. in the style of `.ini` sections)
> In our code, we should never check for a dev or prod specific section of the configuration.
Supported format: YAML, JSON
Supported file extensions: "yml", "yaml", "json"
See other [examples](.config/examples_test.go)
[More detailed design goals](docs/goals.md#Configuration)
### Folders structure for configurations
By default we have:
```
# <- root configuration
{base path}/config.yaml
# <- environment-specifics folder
config.d/
# <- extra configuration to merge
config.yaml
# <- possibly with a modified name: config.*.yaml
config.default.yaml
# <- configuration to merge for environment
{environment}/config.yaml
# other environment-specifics ....
{...}/config.yaml
```
[Here is an example](./config/examples)
When using default settings for this module (these are configurable),
the base path is defined by the `CONFIG_DIR` environment variable.
Secret configurations:
```
{base path}/secrets.yaml
config.d/
# <- secrets to merge
secrets.yaml
# <- configuration to merge for environment
{environment}/secrets.yaml
```
### [Typical configuration for a Kubernetes deployment](docs/k8s.md)
### Side notes
#### TODOs
* [CLI todo list](cli/TODO.md)
* [config todo list](config/TODO.md)
#### Dealing with secrets locally
TODO(fredbi)
### Credits
The config part is largely based on some seminal past work by [@casualjim](https://github.com/casualjim/).
I am grateful to him for his much inspiring code.
The version-from-go-runtime piece of code is largely inspired by the wonderful work from the
[golangci](https://github.com/golangci/golangci-lint) community.