Skip to content

Commit

Permalink
feat: add support for labels in func.yaml (#373)
Browse files Browse the repository at this point in the history
* feat: add support for labels in func.yaml and `func config`

This change adds support for setting labels on deployed functions. It uses
the interactive CLI prompt introduced by Zbynek to add, remove and list
labels applied on a deployed function.

Signed-off-by: Lance Ball <lball@redhat.com>

* fixup: fix string output for Pair type

Signed-off-by: Lance Ball <lball@redhat.com>

* fixup: review feedback

Signed-off-by: Lance Ball <lball@redhat.com>
  • Loading branch information
lance committed Aug 3, 2021
1 parent 578b338 commit 0dba677
Show file tree
Hide file tree
Showing 14 changed files with 905 additions and 167 deletions.
13 changes: 10 additions & 3 deletions cmd/config.go
Expand Up @@ -21,8 +21,9 @@ var configCmd = &cobra.Command{
Short: "Configure a function",
Long: `Configure a function
Interactive propmt that allows configuration of Volume mounts and Environment variables for a function
project present in the current directory or from the directory specified with --path.
Interactive propmt that allows configuration of Volume mounts, Environment
variables, and Labels for a function project present in the current directory
or from the directory specified with --path.
`,
SuggestFor: []string{"cfg", "cofnig"},
PreRunE: bindEnv("path"),
Expand All @@ -41,7 +42,7 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
Name: "selectedConfig",
Prompt: &survey.Select{
Message: "What do you want to configure?",
Options: []string{"Environment values", "Volumes"},
Options: []string{"Environment values", "Volumes", "Labels"},
Default: "Environment values",
},
},
Expand Down Expand Up @@ -74,18 +75,24 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
err = runAddVolumesPrompt(cmd.Context(), function)
} else if answers.SelectedConfig == "Environment values" {
err = runAddEnvsPrompt(cmd.Context(), function)
} else if answers.SelectedConfig == "Labels" {
err = runAddLabelsPrompt(cmd.Context(), function)
}
case "Remove":
if answers.SelectedConfig == "Volumes" {
err = runRemoveVolumesPrompt(function)
} else if answers.SelectedConfig == "Environment values" {
err = runRemoveEnvsPrompt(function)
} else if answers.SelectedConfig == "Labels" {
err = runRemoveLabelsPrompt(function)
}
case "List":
if answers.SelectedConfig == "Volumes" {
listVolumes(function)
} else if answers.SelectedConfig == "Environment values" {
listEnvs(function)
} else if answers.SelectedConfig == "Labels" {
listLabels(function)
}
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/config_envs.go
Expand Up @@ -175,7 +175,7 @@ func runAddEnvsPrompt(ctx context.Context, f fn.Function) (err error) {
return
}

newEnv := fn.Env{}
newEnv := fn.Pair{}

switch selectedOption {
// SECTION - add new Environment variable with the specified value
Expand Down Expand Up @@ -403,7 +403,7 @@ func runRemoveEnvsPrompt(f fn.Function) (err error) {
return
}

var newEnvs fn.Envs
var newEnvs fn.Pairs
removed := false
for i, e := range f.Envs {
if e.String() == selectedEnv {
Expand Down
290 changes: 290 additions & 0 deletions cmd/config_labels.go
@@ -0,0 +1,290 @@
package cmd

import (
"context"
"fmt"
"os"

"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/spf13/cobra"

fn "knative.dev/kn-plugin-func"
"knative.dev/kn-plugin-func/utils"
)

func init() {
configCmd.AddCommand(configLabelsCmd)
configLabelsCmd.Flags().StringP("path", "p", cwd(), "Path to the project directory (Env: $FUNC_PATH)")
configLabelsCmd.AddCommand(configLabelsAddCmd)
configLabelsAddCmd.Flags().StringP("path", "p", cwd(), "Path to the project directory (Env: $FUNC_PATH)")
configLabelsCmd.AddCommand(configLabelsRemoveCmd)
configLabelsRemoveCmd.Flags().StringP("path", "p", cwd(), "Path to the project directory (Env: $FUNC_PATH)")
}

var configLabelsCmd = &cobra.Command{
Use: "labels",
Short: "List and manage configured labels for a function",
Long: `List and manage configured labels for a function
Prints configured labels for a function project present in
the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"albels", "abels", "label"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args)
if err != nil {
return
}

listLabels(function)

return
},
}

var configLabelsAddCmd = &cobra.Command{
Use: "add",
Short: "Add labels to the function configuration",
Long: `Add labels to the function configuration
Interactive prompt to add labels to the function project in the current
directory or from the directory specified with --path.
The label can be set directly from a value or from an environment variable on
the local machine.
`,
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 runAddLabelsPrompt(cmd.Context(), function)
},
}

var configLabelsRemoveCmd = &cobra.Command{
Use: "remove",
Short: "Remove labels from the function configuration",
Long: `Remove labels from the function configuration
Interactive prompt to remove labels 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 runRemoveLabelsPrompt(function)
},
}

func listLabels(f fn.Function) {
if len(f.Labels) == 0 {
fmt.Println("There aren't any configured labels")
return
}

fmt.Println("Configured labels:")
for _, e := range f.Labels {
fmt.Println(" - ", e.String())
}
}

func runAddLabelsPrompt(ctx context.Context, f fn.Function) (err error) {

insertToIndex := 0

// SECTION - if there are some labels already set, choose the position of the new entry
if len(f.Labels) > 0 {
options := []string{}
for _, e := range f.Labels {
options = append(options, fmt.Sprintf("Insert before: %s", e.String()))
}
options = append(options, "Insert here.")

selectedLabel := ""
prompt := &survey.Select{
Message: "Where do you want to add the label?",
Options: options,
Default: options[len(options)-1],
}
err = survey.AskOne(prompt, &selectedLabel)
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return
}

for i, option := range options {
if option == selectedLabel {
insertToIndex = i
break
}
}
}

// SECTION - select the type of label to be added
selectedOption := ""
const (
optionLabelValue = "Label with a specified value"
optionLabelLocal = "Value from a local environment variable"
)
options := []string{optionLabelValue, optionLabelLocal}

err = survey.AskOne(&survey.Select{
Message: "What type of label do you want to add?",
Options: options,
}, &selectedOption)
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return
}

newPair := fn.Pair{}

switch selectedOption {
// SECTION - add new label with the specified value
case optionLabelValue:
qs := []*survey.Question{
{
Name: "name",
Prompt: &survey.Input{Message: "Please specify the label name:"},
Validate: func(val interface{}) error {
return utils.ValidateLabelName(val.(string))
},
},
{
Name: "value",
Prompt: &survey.Input{Message: "Please specify the label value:"},
Validate: func(val interface{}) error {
return utils.ValidateLabelValue(val.(string))
}},
}
answers := struct {
Name string
Value string
}{}

err = survey.Ask(qs, &answers)
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return
}

newPair.Name = &answers.Name
newPair.Value = &answers.Value

// SECTION - add new label with value from a local environment variable
case optionLabelLocal:
qs := []*survey.Question{
{
Name: "name",
Prompt: &survey.Input{Message: "Please specify the label name:"},
Validate: func(val interface{}) error {
return utils.ValidateLabelName(val.(string))
},
},
{
Name: "value",
Prompt: &survey.Input{Message: "Please specify the local environment variable:"},
Validate: func(val interface{}) error {
return utils.ValidateLabelValue(val.(string))
},
},
}
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)
newPair.Name = &answers.Name
newPair.Value = &value
}

// we have all necessary information -> let's insert the label to the selected position in the list
if insertToIndex == len(f.Labels) {
f.Labels = append(f.Labels, newPair)
} else {
f.Labels = append(f.Labels[:insertToIndex+1], f.Labels[insertToIndex:]...)
f.Labels[insertToIndex] = newPair
}

err = f.WriteConfig()
if err == nil {
fmt.Println("Label entry was added to the function configuration")
}

return
}

func runRemoveLabelsPrompt(f fn.Function) (err error) {
if len(f.Labels) == 0 {
fmt.Println("There aren't any configured labels")
return
}

options := []string{}
for _, e := range f.Labels {
options = append(options, e.String())
}

selectedLabel := ""
prompt := &survey.Select{
Message: "Which labels do you want to remove?",
Options: options,
}
err = survey.AskOne(prompt, &selectedLabel)
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return
}

var newLabels fn.Pairs
removed := false
for i, e := range f.Labels {
if e.String() == selectedLabel {
newLabels = append(f.Labels[:i], f.Labels[i+1:]...)
removed = true
break
}
}

if removed {
f.Labels = newLabels
err = f.WriteConfig()
if err == nil {
fmt.Println("Label was removed from the function configuration")
}
}

return
}
1 change: 1 addition & 0 deletions cmd/delete_test.go
Expand Up @@ -62,6 +62,7 @@ builderMap:
default: quay.io/boson/faas-go-builder
envs: []
annotations: {}
labels: []
`
if err := ioutil.WriteFile("func.yaml", []byte(funcYaml), 0600); err != nil {
t.Fatal(err)
Expand Down
6 changes: 3 additions & 3 deletions cmd/root.go
Expand Up @@ -276,7 +276,7 @@ func envFromCmd(cmd *cobra.Command) (*util.OrderedMap, []string, error) {
return util.NewOrderedMap(), []string{}, nil
}

func mergeEnvs(envs fn.Envs, envToUpdate *util.OrderedMap, envToRemove []string) (fn.Envs, error) {
func mergeEnvs(envs fn.Pairs, envToUpdate *util.OrderedMap, envToRemove []string) (fn.Pairs, error) {
updated := sets.NewString()

for i := range envs {
Expand All @@ -294,7 +294,7 @@ func mergeEnvs(envs fn.Envs, envToUpdate *util.OrderedMap, envToRemove []string)
if !updated.Has(name) {
n := name
v := value
envs = append(envs, fn.Env{Name: &n, Value: &v})
envs = append(envs, fn.Pair{Name: &n, Value: &v})
}
}

Expand All @@ -309,7 +309,7 @@ func mergeEnvs(envs fn.Envs, envToUpdate *util.OrderedMap, envToRemove []string)

errMsg := fn.ValidateEnvs(envs)
if len(errMsg) > 0 {
return fn.Envs{}, fmt.Errorf(strings.Join(errMsg, "\n"))
return fn.Pairs{}, fmt.Errorf(strings.Join(errMsg, "\n"))
}

return envs, nil
Expand Down

0 comments on commit 0dba677

Please sign in to comment.