func/cmd/config_envs.go

511 lines
13 KiB
Go

package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/ory/viper"
"github.com/spf13/cobra"
"knative.dev/func/pkg/config"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/utils"
)
func NewConfigEnvsCmd(loadSaver functionLoaderSaver) *cobra.Command {
cmd := &cobra.Command{
Use: "envs",
Short: "List and manage configured environment variable for a function",
Long: `List and manage configured environment variable for a function
Prints configured Environment variable for a function project present in
the current directory or from the directory specified with --path.
`,
Aliases: []string{"env"},
SuggestFor: []string{"ensv"},
PreRunE: bindEnv("path", "output", "verbose"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(loadSaver)
if err != nil {
return
}
return listEnvs(function, cmd.OutOrStdout(), Format(viper.GetString("output")))
},
}
cfg, err := config.NewDefault()
if err != nil {
fmt.Fprintf(cmd.OutOrStdout(), "error loading config at '%v'. %v\n", config.File(), err)
}
cmd.Flags().StringP("output", "o", "human", "Output format (human|json) ($FUNC_OUTPUT)")
configEnvsAddCmd := NewConfigEnvsAddCmd(loadSaver)
configEnvsRemoveCmd := NewConfigEnvsRemoveCmd()
addPathFlag(cmd)
addPathFlag(configEnvsAddCmd)
addPathFlag(configEnvsRemoveCmd)
addVerboseFlag(cmd, cfg.Verbose)
addVerboseFlag(configEnvsAddCmd, cfg.Verbose)
addVerboseFlag(configEnvsRemoveCmd, cfg.Verbose)
cmd.AddCommand(configEnvsAddCmd)
cmd.AddCommand(configEnvsRemoveCmd)
return cmd
}
func NewConfigEnvsAddCmd(loadSaver functionLoaderSaver) *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Short: "Add environment variable to the function configuration",
Long: `Add environment variable to the function configuration.
If environment variable is not set explicitly by flag, interactive prompt is used.
The environment variable can be set directly from a value,
from an environment variable on the local machine or from Secrets and ConfigMaps.
It is also possible to import all keys as environment variables from a Secret or ConfigMap.`,
Example: `# set environment variable directly
{{rootCmdUse}} config envs add --name=VARNAME --value=myValue
# set environment variable from local env $LOC_ENV
{{rootCmdUse}} config envs add --name=VARNAME --value='{{"{{"}} env:LOC_ENV {{"}}"}}'
set environment variable from a secret
{{rootCmdUse}} config envs add --name=VARNAME --value='{{"{{"}} secret:secretName:key {{"}}"}}'
# set all key as environment variables from a secret
{{rootCmdUse}} config envs add --value='{{"{{"}} secret:secretName {{"}}"}}'
# set environment variable from a configMap
{{rootCmdUse}} config envs add --name=VARNAME --value='{{"{{"}} configMap:confMapName:key {{"}}"}}'
# set all key as environment variables from a configMap
{{rootCmdUse}} config envs add --value='{{"{{"}} configMap:confMapName {{"}}"}}'`,
SuggestFor: []string{"ad", "create", "insert", "append"},
PreRunE: bindEnv("path", "name", "value", "verbose"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(loadSaver)
if err != nil {
return
}
var np *string
var vp *string
if cmd.Flags().Changed("name") {
s, e := cmd.Flags().GetString("name")
if e != nil {
return e
}
np = &s
}
if cmd.Flags().Changed("value") {
s, e := cmd.Flags().GetString("value")
if e != nil {
return e
}
vp = &s
}
if np != nil || vp != nil {
if np != nil {
if err := utils.ValidateEnvVarName(*np); err != nil {
return err
}
}
function.Run.Envs = append(function.Run.Envs, fn.Env{Name: np, Value: vp})
return loadSaver.Save(function)
}
return runAddEnvsPrompt(cmd.Context(), function)
},
}
cmd.Flags().StringP("name", "", "", "Name of the environment variable.")
cmd.Flags().StringP("value", "", "", "Value of the environment variable.")
return cmd
}
func NewConfigEnvsRemoveCmd() *cobra.Command {
return &cobra.Command{
Use: "remove",
Short: "Remove environment variable from the function configuration",
Long: `Remove environment variable from the function configuration
Interactive prompt to remove Environment variables from the function project
in the current directory or from the directory specified with --path.
`,
Aliases: []string{"rm"},
SuggestFor: []string{"del", "delete", "rmeove"},
PreRunE: bindEnv("path", "verbose"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(defaultLoaderSaver)
if err != nil {
return
}
return runRemoveEnvsPrompt(function)
},
}
}
func listEnvs(f fn.Function, w io.Writer, outputFormat Format) error {
switch outputFormat {
case Human:
if len(f.Run.Envs) == 0 {
_, err := fmt.Fprintln(w, "There aren't any configured Environment variables")
return err
}
fmt.Println("Configured Environment variables:")
for _, e := range f.Run.Envs {
_, err := fmt.Fprintln(w, " - ", e.String())
if err != nil {
return err
}
}
return nil
case JSON:
enc := json.NewEncoder(w)
return enc.Encode(f.Run.Envs)
default:
return fmt.Errorf("bad format: %v", outputFormat)
}
}
func runAddEnvsPrompt(ctx context.Context, f fn.Function) (err error) {
insertToIndex := 0
// SECTION - if there are some envs already set, let choose the position of the new entry
if len(f.Run.Envs) > 0 {
options := []string{}
for _, e := range f.Run.Envs {
options = append(options, fmt.Sprintf("Insert before: %s", e.String()))
}
options = append(options, "Insert here.")
selectedEnv := ""
prompt := &survey.Select{
Message: "Where do you want to add the Environment variable?",
Options: options,
Default: options[len(options)-1],
}
err = survey.AskOne(prompt, &selectedEnv)
if err != nil {
return
}
for i, option := range options {
if option == selectedEnv {
insertToIndex = i
break
}
}
}
// SECTION - select the type of Environment variable to be added
secrets, err := k8s.ListSecretsNamesIfConnected(ctx, f.Deploy.Namespace)
if err != nil {
return
}
configMaps, err := k8s.ListConfigMapsNamesIfConnected(ctx, f.Deploy.Namespace)
if err != nil {
return
}
selectedOption := ""
const (
optionEnvValue = "Environment variable with a specified value"
optionEnvLocal = "Value from a local environment variable"
optionEnvConfigMap = "ConfigMap: all key=value pairs as environment variables"
optionEnvConfigMapKey = "ConfigMap: value from a key"
optionEnvSecret = "Secret: all key=value pairs as environment variables"
optionEnvSecretKey = "Secret: value from a key"
)
options := []string{optionEnvValue, optionEnvLocal}
if len(configMaps) > 0 {
options = append(options, optionEnvConfigMap)
options = append(options, optionEnvConfigMapKey)
}
if len(secrets) > 0 {
options = append(options, optionEnvSecret)
options = append(options, optionEnvSecretKey)
}
err = survey.AskOne(&survey.Select{
Message: "What type of Environment variable do you want to add?",
Options: options,
}, &selectedOption)
if err != nil {
return
}
newEnv := fn.Env{}
switch selectedOption {
// SECTION - add new Environment variable with the specified value
case optionEnvValue:
qs := []*survey.Question{
{
Name: "name",
Prompt: &survey.Input{Message: "Please specify the Environment variable name:"},
Validate: func(val interface{}) error {
return utils.ValidateEnvVarName(val.(string))
},
},
{
Name: "value",
Prompt: &survey.Input{Message: "Please specify the Environment variable value:"},
},
}
answers := struct {
Name string
Value string
}{}
err = survey.Ask(qs, &answers)
if err != nil {
if errors.Is(err, terminal.InterruptErr) {
return nil
}
return
}
newEnv.Name = &answers.Name
newEnv.Value = &answers.Value
// SECTION - add new Environment variable with value from a local environment variable
case optionEnvLocal:
qs := []*survey.Question{
{
Name: "name",
Prompt: &survey.Input{Message: "Please specify the Environment variable name:"},
Validate: func(val interface{}) error {
return utils.ValidateEnvVarName(val.(string))
},
},
{
Name: "value",
Prompt: &survey.Input{Message: "Please specify the local Environment variable:"},
Validate: survey.Required,
},
}
answers := struct {
Name string
Value string
}{}
err = survey.Ask(qs, &answers)
if err != nil {
if errors.Is(err, terminal.InterruptErr) {
return nil
}
return
}
if _, ok := os.LookupEnv(answers.Value); !ok {
fmt.Printf("Warning: specified local environment variable %q is not set\n", answers.Value)
}
value := fmt.Sprintf("{{ env:%s }}", answers.Value)
newEnv.Name = &answers.Name
newEnv.Value = &value
// SECTION - Add all key=value pairs from ConfigMap as environment variables
case optionEnvConfigMap:
selectedResource := ""
err = survey.AskOne(&survey.Select{
Message: "Which ConfigMap do you want to use?",
Options: configMaps,
}, &selectedResource)
if err != nil {
if errors.Is(err, terminal.InterruptErr) {
return nil
}
return
}
value := fmt.Sprintf("{{ configMap:%s }}", selectedResource)
newEnv.Value = &value
// SECTION - Environment variable with value from a key from ConfigMap
case optionEnvConfigMapKey:
qs := []*survey.Question{
{
Name: "configmap",
Prompt: &survey.Select{
Message: "Which ConfigMap do you want to use?",
Options: configMaps,
},
},
{
Name: "name",
Prompt: &survey.Input{Message: "Please specify the Environment variable name:"},
Validate: func(val interface{}) error {
return utils.ValidateEnvVarName(val.(string))
},
},
{
Name: "key",
Prompt: &survey.Input{Message: "Please specify a key from the selected ConfigMap:"},
Validate: func(val interface{}) error {
return utils.ValidateConfigMapKey(val.(string))
},
},
}
answers := struct {
ConfigMap string
Name string
Key string
}{}
err = survey.Ask(qs, &answers)
if err != nil {
if errors.Is(err, terminal.InterruptErr) {
return nil
}
return
}
value := fmt.Sprintf("{{ configMap:%s:%s }}", answers.ConfigMap, answers.Key)
newEnv.Name = &answers.Name
newEnv.Value = &value
// SECTION - Add all key=value pairs from Secret as environment variables
case optionEnvSecret:
selectedResource := ""
err = survey.AskOne(&survey.Select{
Message: "Which Secret do you want to use?",
Options: secrets,
}, &selectedResource)
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return
}
value := fmt.Sprintf("{{ secret:%s }}", selectedResource)
newEnv.Value = &value
// SECTION - Environment variable with value from a key from Secret
case optionEnvSecretKey:
qs := []*survey.Question{
{
Name: "secret",
Prompt: &survey.Select{
Message: "Which Secret do you want to use?",
Options: secrets,
},
},
{
Name: "name",
Prompt: &survey.Input{Message: "Please specify the Environment variable name:"},
Validate: func(val interface{}) error {
return utils.ValidateEnvVarName(val.(string))
},
},
{
Name: "key",
Prompt: &survey.Input{Message: "Please specify a key from the selected Secret:"},
Validate: func(val interface{}) error {
return utils.ValidateSecretKey(val.(string))
},
},
}
answers := struct {
Secret string
Name string
Key string
}{}
err = survey.Ask(qs, &answers)
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return
}
value := fmt.Sprintf("{{ secret:%s:%s }}", answers.Secret, answers.Key)
newEnv.Name = &answers.Name
newEnv.Value = &value
}
// we have all necessary information -> let's insert the env to the selected position in the list
if insertToIndex == len(f.Run.Envs) {
f.Run.Envs = append(f.Run.Envs, newEnv)
} else {
f.Run.Envs = append(f.Run.Envs[:insertToIndex+1], f.Run.Envs[insertToIndex:]...)
f.Run.Envs[insertToIndex] = newEnv
}
err = f.Write()
if err == nil {
fmt.Println("Environment variable entry was added to the function configuration")
}
return
}
func runRemoveEnvsPrompt(f fn.Function) (err error) {
if len(f.Run.Envs) == 0 {
fmt.Println("There aren't any configured Environment variables")
return
}
options := []string{}
for _, e := range f.Run.Envs {
options = append(options, e.String())
}
selectedEnv := ""
prompt := &survey.Select{
Message: "Which Environment variables do you want to remove?",
Options: options,
}
err = survey.AskOne(prompt, &selectedEnv)
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return
}
var newEnvs []fn.Env
removed := false
for i, e := range f.Run.Envs {
if e.String() == selectedEnv {
newEnvs = append(f.Run.Envs[:i], f.Run.Envs[i+1:]...)
removed = true
break
}
}
if removed {
f.Run.Envs = newEnvs
err = f.Write()
if err == nil {
fmt.Println("Environment variable entry was removed from the function configuration")
}
}
return
}