feat: `func config envs` - interactive prompt (#396)

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>
This commit is contained in:
Zbynek Roubalik 2021-06-21 08:37:48 +02:00 committed by GitHub
parent 4711638495
commit 83a9ca684f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 486 additions and 8 deletions

View File

@ -72,14 +72,20 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
case "Add":
if answers.SelectedConfig == "Volumes" {
err = runAddVolumesPrompt(cmd.Context(), function)
} else if answers.SelectedConfig == "Environment values" {
err = runAddEnvsPrompt(cmd.Context(), function)
}
case "Remove":
if answers.SelectedConfig == "Volumes" {
err = runRemoveVolumesPrompt(function)
} else if answers.SelectedConfig == "Environment values" {
err = runRemoveEnvsPrompt(function)
}
case "List":
if answers.SelectedConfig == "Volumes" {
listVolumes(function)
} else if answers.SelectedConfig == "Environment values" {
listEnvs(function)
}
}

425
cmd/config_envs.go Normal file
View File

@ -0,0 +1,425 @@
package cmd
import (
"context"
"fmt"
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/spf13/cobra"
bosonFunc "github.com/boson-project/func"
"github.com/boson-project/func/k8s"
"github.com/boson-project/func/utils"
)
func init() {
configCmd.AddCommand(configEnvsCmd)
configEnvsCmd.Flags().StringP("path", "p", cwd(), "Path to the project directory (Env: $FUNC_PATH)")
configEnvsCmd.AddCommand(configEnvsAddCmd)
configEnvsAddCmd.Flags().StringP("path", "p", cwd(), "Path to the project directory (Env: $FUNC_PATH)")
configEnvsCmd.AddCommand(configEnvsRemoveCmd)
configEnvsRemoveCmd.Flags().StringP("path", "p", cwd(), "Path to the project directory (Env: $FUNC_PATH)")
}
var configEnvsCmd = &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.
`,
SuggestFor: []string{"ensv", "env"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args)
if err != nil {
return
}
listEnvs(function)
return
},
}
var configEnvsAddCmd = &cobra.Command{
Use: "add",
Short: "Add environment variable to the function configuration",
Long: `Add environment variable to the function configuration
Interactive prompt to add Environment variables to the function project
in the current directory or from the directory specified with --path.
The environment variable can be set directly from a value,
from an environment variable on the local machine or from Secrets and ConfigMaps.
`,
SuggestFor: []string{"ad", "create", "insert", "append"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args)
if err != nil {
return
}
return runAddEnvsPrompt(cmd.Context(), function)
},
}
var configEnvsRemoveCmd = &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.
`,
SuggestFor: []string{"del", "delete", "rmeove"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args)
if err != nil {
return
}
return runRemoveEnvsPrompt(function)
},
}
func listEnvs(f bosonFunc.Function) {
if len(f.Envs) == 0 {
fmt.Println("There aren't any configured Environment variables")
return
}
fmt.Println("Configured Environment variables:")
for _, e := range f.Envs {
fmt.Println(" - ", e.String())
}
}
func runAddEnvsPrompt(ctx context.Context, f bosonFunc.Function) (err error) {
insertToIndex := 0
// SECTION - if there are some envs already set, let choose the position of the new entry
if len(f.Envs) > 0 {
options := []string{}
for _, e := range f.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 {
if err == terminal.InterruptErr {
return 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.ListSecretsNames(ctx, f.Namespace)
if err != nil {
return
}
configMaps, err := k8s.ListConfigMapsNames(ctx, f.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 {
if err == terminal.InterruptErr {
return nil
}
return
}
newEnv := bosonFunc.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 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 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 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: survey.Required,
},
}
answers := struct {
ConfigMap string
Name string
Key string
}{}
err = survey.Ask(qs, &answers)
if err != nil {
if 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: survey.Required,
},
}
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.Envs) {
f.Envs = append(f.Envs, newEnv)
} else {
f.Envs = append(f.Envs[:insertToIndex+1], f.Envs[insertToIndex:]...)
f.Envs[insertToIndex] = newEnv
}
err = f.WriteConfig()
if err == nil {
fmt.Println("Environment variable entry was added to the function configuration")
}
return
}
func runRemoveEnvsPrompt(f bosonFunc.Function) (err error) {
if len(f.Envs) == 0 {
fmt.Println("There aren't any configured Environment variables")
return
}
options := []string{}
for _, e := range f.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 bosonFunc.Envs
removed := false
for i, e := range f.Envs {
if e.String() == selectedEnv {
newEnvs = append(f.Envs[:i], f.Envs[i+1:]...)
removed = true
break
}
}
if removed {
f.Envs = newEnvs
err = f.WriteConfig()
if err == nil {
fmt.Println("Environment variable entry was removed from the function configuration")
}
}
return
}

View File

@ -53,8 +53,7 @@ Interactive prompt to add Secrets and ConfigMaps as Volume mounts to the functio
in the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"ad", "create", "insert", "append"},
//ValidArgsFunction: CompleteFunctionList,
PreRunE: bindEnv("path"),
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args)
if err != nil {
@ -192,10 +191,10 @@ func runAddVolumesPrompt(ctx context.Context, f bosonFunc.Function) (err error)
}
f.Volumes = append(f.Volumes, newVolume)
err = f.WriteConfig()
if err == nil {
fmt.Println("Volume was added to the function configuration")
fmt.Println("Volume entry was added to the function configuration")
}
return
@ -239,7 +238,7 @@ func runRemoveVolumesPrompt(f bosonFunc.Function) (err error) {
f.Volumes = newVolumes
err = f.WriteConfig()
if err == nil {
fmt.Println("Volume was removed from the function configuration")
fmt.Println("Volume entry was removed from the function configuration")
}
}

View File

@ -47,6 +47,35 @@ type Env struct {
Value *string `yaml:"value"`
}
func (e Env) String() string {
if e.Name == nil && e.Value != nil {
match := regWholeSecret.FindStringSubmatch(*e.Value)
if len(match) == 2 {
return fmt.Sprintf("All key=value pairs from Secret \"%s\"", match[1])
}
match = regWholeConfigMap.FindStringSubmatch(*e.Value)
if len(match) == 2 {
return fmt.Sprintf("All key=value pairs from ConfigMap \"%s\"", match[1])
}
} else if e.Name != nil && e.Value != nil {
match := regKeyFromSecret.FindStringSubmatch(*e.Value)
if len(match) == 3 {
return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from Secret \"%s\"", *e.Name, match[2], match[1])
}
match = regKeyFromConfigMap.FindStringSubmatch(*e.Value)
if len(match) == 3 {
return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from ConfigMap \"%s\"", *e.Name, match[2], match[1])
}
match = regLocalEnv.FindStringSubmatch(*e.Value)
if len(match) == 2 {
return fmt.Sprintf("Env \"%s\" with value set from local env variable \"%s\"", *e.Name, match[1])
}
return fmt.Sprintf("Env \"%s\" with value \"%s\"", *e.Name, *e.Value)
}
return ""
}
type Options struct {
Scale *ScaleOptions `yaml:"scale,omitempty"`
}

View File

@ -185,17 +185,36 @@ Configured Volumes mounts:
- ConfigMap "mycm" mounted at path: "/workspace/configmap"
```
### `config envs`
This command lists configured Environment variables:
```console
func config envs [-p <path>]
```
Invokes interactive prompt to add Environment variables to the function configuration
```console
func config envs add [-p <path>]
```
Invokes interactive prompt to remove Environment variables from the function configuration
```console
func config envs remove [-p <path>]
```
### `config volumes`
This command lists configured Volumes:
```console
func config volumes [-p <path>]
```
Invokes interactive prompt that allows addind Volumes to the function configuration
Invokes interactive prompt to add Volumes to the function configuration
```console
func config volumes add [-p <path>]
```
Invokes interactive prompt that allows removing Volumes from the function configuration
Invokes interactive prompt to remove Volumes from the function configuration
```console
func config volumes remove [-p <path>]
```
```