1
0
mirror of https://github.com/smallstep/cli.git synced 2025-08-09 03:22:43 +03:00
Files
step-ca-cli/command/command.go
Dominik Roos 8de234779b feedback
2021-12-02 22:38:16 +01:00

241 lines
5.5 KiB
Go

package command
import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"github.com/pkg/errors"
"github.com/smallstep/cli/crypto/fingerprint"
"github.com/smallstep/cli/usage"
"github.com/urfave/cli"
"go.step.sm/cli-utils/step"
)
// IgnoreEnvVar is a value added to a flag EnvVar to avoid the use of
// environment variables or configuration files.
const IgnoreEnvVar = "STEP_IGNORE_ENV_VAR"
var cmds []cli.Command
var currentContext *cli.Context
func init() {
os.Unsetenv(IgnoreEnvVar)
cmds = []cli.Command{
usage.HelpCommand(),
}
}
// Register adds the given command to the global list of commands.
// It sets recursively the command Flags environment variables.
func Register(c cli.Command) {
setEnvVar(&c)
cmds = append(cmds, c)
}
// Retrieve returns all commands
func Retrieve() []cli.Command {
return cmds
}
// ActionFunc returns a cli.ActionFunc that stores the context.
func ActionFunc(fn cli.ActionFunc) cli.ActionFunc {
return func(ctx *cli.Context) error {
currentContext = ctx
return fn(ctx)
}
}
// IsForce returns if the force flag was passed
func IsForce() bool {
return currentContext != nil && currentContext.Bool("force")
}
// getConfigVars load the defaults.json file and sets the flags if they are not
// already set or the EnvVar is set to IgnoreEnvVar.
//
// TODO(mariano): right now it only supports parameters at first level.
func getConfigVars(ctx *cli.Context) error {
configFile := ctx.GlobalString("config")
if configFile == "" {
configFile = step.DefaultsFile()
}
b, err := os.ReadFile(configFile)
if err != nil {
return nil
}
m := make(map[string]interface{})
if err := json.Unmarshal(b, &m); err != nil {
return errors.Wrapf(err, "error parsing %s", configFile)
}
flags := make(map[string]cli.Flag)
for _, f := range ctx.Command.Flags {
name := strings.Split(f.GetName(), ",")[0]
flags[name] = f
}
for _, name := range ctx.FlagNames() {
if ctx.IsSet(name) {
continue
}
// Skip if EnvVar == IgnoreEnvVar
if f, ok := flags[name]; ok {
if getFlagEnvVar(f) == IgnoreEnvVar {
continue
}
}
if v, ok := m[name]; ok {
ctx.Set(name, fmt.Sprintf("%v", v))
}
}
return nil
}
// getEnvVar generates the environment variable for the given flag name.
func getEnvVar(name string) string {
parts := strings.Split(name, ",")
name = strings.TrimSpace(parts[0])
name = strings.ReplaceAll(name, "-", "_")
return "STEP_" + strings.ToUpper(name)
}
// getFlagEnvVar returns the value of the EnvVar field of a flag.
func getFlagEnvVar(f cli.Flag) string {
v := reflect.ValueOf(f)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() == reflect.Struct {
envVar := v.FieldByName("EnvVar")
if envVar.IsValid() {
return envVar.String()
}
}
return ""
}
// setEnvVar sets the the EnvVar element to each flag recursively.
func setEnvVar(c *cli.Command) {
if c == nil {
return
}
// Enable getting the flags from a json file
if c.Before == nil && c.Action != nil {
c.Before = getConfigVars
}
// Enable getting the flags from environment variables
for i := range c.Flags {
envVar := getEnvVar(c.Flags[i].GetName())
switch f := c.Flags[i].(type) {
case cli.BoolFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.BoolTFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.DurationFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.Float64Flag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.GenericFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.Int64Flag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.IntFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.IntSliceFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.Int64SliceFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.StringFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.StringSliceFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.Uint64Flag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
case cli.UintFlag:
if f.EnvVar == "" {
f.EnvVar = envVar
c.Flags[i] = f
}
}
}
for i := range c.Subcommands {
setEnvVar(&c.Subcommands[i])
}
}
// FingerprintFormatFlag returns a flag for configuring the fingerprint format.
func FingerprintFormatFlag(defaultFmt string) cli.StringFlag {
return cli.StringFlag{
Name: "format",
Usage: `The <format> of the fingerprint, it must be "hex", "base64", "base64-url", "base64-raw", "base64-url-raw" or "emoji".`,
Value: defaultFmt,
}
}
// GetFingerprintEncoding gets the fingerprint encoding from the format flag.
func GetFingerprintEncoding(format string) (fingerprint.Encoding, error) {
switch strings.ToLower(strings.TrimSpace(format)) {
case "hex", "":
return fingerprint.HexFingerprint, nil
case "base64":
return fingerprint.Base64StdFingerprint, nil
case "base64url", "base64-url":
return fingerprint.Base64URLFingerprint, nil
case "base64urlraw", "base64url-raw", "base64-url-raw":
return fingerprint.Base64RawURLFingerprint, nil
case "base64raw", "base64-raw":
return fingerprint.Base64RawStdFingerprint, nil
case "emoji", "emojisum":
return fingerprint.EmojiFingerprint, nil
default:
return 0, errors.Errorf("error parsing fingerprint format: '%s' is not a valid fingerprint format", format)
}
}