cli icon indicating copy to clipboard operation
cli copied to clipboard

Map sub-command to nested yaml elements

Open lburgazzoli opened this issue 3 years ago • 8 comments

my question is...

( how can I bind flags to nested yaml elements ? )

I'm evaluation urfave/cli - so I'm a newbiew here - as foundation for the new camel-k cli which is now based on spf13/cobra and I'm trying to figure out how to property bind sub-command flags from a nested yaml structure.

As example the camel-k cli has a run sub command with it's own flags like dependency and I want to be able to load values for such flags from a YAML like:

run:
  dependencies:
  - foo
  - bar

I've created the following example:

func main() {
	var kubeconf string
	var namespace string
	var dependencies cli.StringSlice

	flags := []cli.Flag{
		&cli.StringFlag{
			Name:  "kamelconfig",
			Usage: "The location of the kamel cli configuration",
			Value: ".kamel/config.yaml",
		},
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:        "kubeconf",
			Usage:       "The path to the kubernetes config file to use for CLI requests",
			Destination: &kubeconf,
			EnvVars:     []string{"KUBECONFIG"},
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:        "namespace",
			Aliases:     []string{"n"},
			Usage:       "The `namespace` to use for all operations",
			Destination: &namespace,
		}),
	}

	runFlags := []cli.Flag{
		altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
			Name:        "dependency",
			Usage:       "dependency",
			Destination: &dependencies,
		}),
	}

	app := &cli.App{
		Name:                 "kamel",
		Usage:                "kamel",
		EnableBashCompletion: true,
		Before:               altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("kamelconfig")),
		Flags:                flags,
		Commands: []*cli.Command{
			{
				Name:  "run",
				Usage: "run",
				Flags: runFlags,
				Action: func(c *cli.Context) error {
					fmt.Println(dependencies)
					return nil
				},
			},
		},
		Action: func(c *cli.Context) error {
			fmt.Println(kubeconf)
			fmt.Println(namespace)
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

I can't figure out how I can achieve what I'm looking for as:

  1. the Before option is set on the root command: do I need to set it on each command ?
  2. is there a way to set the name of the field used to search in the yaml source ? as example here I'd like to set that the dependency get translated to dependencies and the full key should be run.dependencies to properly navigate the path of the source

btw, I'm not even sure if it is possible

lburgazzoli avatar Oct 12 '20 17:10 lburgazzoli

question +1

ghostbody avatar Nov 29 '20 13:11 ghostbody

I'm also curious about this...

zonnie avatar Jan 18 '21 06:01 zonnie

This issue or PR has been automatically marked as stale because it has not had recent activity. Please add a comment bumping this if you're still interested in it's resolution! Thanks for your help, please let us know if you need anything else.

stale[bot] avatar Apr 18 '21 22:04 stale[bot]

Nope

frederikhors avatar Apr 18 '21 23:04 frederikhors

This issue or PR has been bumped and is no longer marked as stale! Feel free to bump it again in the future, if it's still relevant.

stale[bot] avatar Apr 18 '21 23:04 stale[bot]

This issue or PR has been automatically marked as stale because it has not had recent activity. Please add a comment bumping this if you're still interested in it's resolution! Thanks for your help, please let us know if you need anything else.

stale[bot] avatar Jul 19 '21 19:07 stale[bot]

Nope

frederikhors avatar Jul 19 '21 19:07 frederikhors

This issue or PR has been bumped and is no longer marked as stale! Feel free to bump it again in the future, if it's still relevant.

stale[bot] avatar Jul 19 '21 19:07 stale[bot]

@lburgazzoli I added two lines to your code

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/urfave/cli/v2"
	"github.com/urfave/cli/v2/altsrc"
)

func main() {
	var kubeconf string
	var namespace string
	var dependencies cli.StringSlice

	flags := []cli.Flag{
		&cli.StringFlag{
			Name:  "kamelconfig",
			Usage: "The location of the kamel cli configuration",
			Value: ".kamel/config.yaml",
		},
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:        "kubeconf",
			Usage:       "The path to the kubernetes config file to use for CLI requests",
			Destination: &kubeconf,
			EnvVars:     []string{"KUBECONFIG"},
		}),
		altsrc.NewStringFlag(&cli.StringFlag{
			Name:        "namespace",
			Aliases:     []string{"n"},
			Usage:       "The `namespace` to use for all operations",
			Destination: &namespace,
		}),
	}

	runFlags := []cli.Flag{
		altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
			Name:        "dependency",
			Usage:       "dependency",
			Aliases:     []string{"run.dependencies"},  // added this line
			Destination: &dependencies,
		}),
	}

	flags = append(flags, runFlags...)   // added this line

	app := &cli.App{
		Name:                 "kamel",
		Usage:                "kamel",
		EnableBashCompletion: true,
		Before:               altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("kamelconfig")),
		Flags:                flags,
		Commands: []*cli.Command{
			{
				Name:  "run",
				Usage: "run",
				Flags: runFlags,
				Action: func(c *cli.Context) error {
					fmt.Println(dependencies)
					return nil
				},
			},
		},
		Action: func(c *cli.Context) error {
			fmt.Println(kubeconf)
			fmt.Println(namespace)
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

and updated your .kamel/config.yaml as follows

$ cat .kamel/config.yaml 
kubeconf: hello
namespace: dd
run:
  dependencies:
  - foo
  - bar

This is what I get when I run the code

$ go run main.go run
{[foo bar] true}
$ go run main.go 
hello
dd

dearchap avatar Oct 19 '22 15:10 dearchap

@dearchap thanks for looking into this, two notes:

  1. dogin flags = append(flags, runFlags...) seems counter intuitive as the runFlags do not belong to the root command
  2. adding run.dependencies as an alias has a side effect right ? so one can do things like kamrl run --run.dependencies=foo

lburgazzoli avatar Oct 20 '22 07:10 lburgazzoli

@lburgazzoli This would work

	app := &cli.App{
		Name:                 "kamel",
		Usage:                "kamel",
		EnableBashCompletion: true,
		Before:               altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("kamelconfig")),
		Flags:                flags,
		Commands: []*cli.Command{
			{
				Name:   "run",
				Usage:  "run",
				Flags:  runFlags,
				Before: altsrc.InitInputSourceWithContext(runFlags, altsrc.NewYamlSourceFromFlagFunc("kamelconfig")),
				Action: func(c *cli.Context) error {
					fmt.Println(dependencies)
					return nil
				},
			},
		},
		Action: func(c *cli.Context) error {
			fmt.Println(kubeconf)
			fmt.Println(namespace)
			return nil
		},
	}

Yes you are correct in that you can use --run.dependencies=foo on command line. Actually that is the correct behaviour of alias. The side effect is in traversing the json using that path.

What is the experience that you are looking for ?

dearchap avatar Oct 20 '22 14:10 dearchap

What is the experience that you are looking for ?

I think what I see here is good enough, As a suggestion, it would be nice if NewYamlSourceFromFlagFunc would accept also some sort of root node, like:

    Before: altsrc.InitInputSourceWithContext(
                    runFlags, 
                    altsrc.NewYamlSourceFromFlagFunc("kamelconfig", "run")),

lburgazzoli avatar Oct 20 '22 14:10 lburgazzoli

The flag is searched up the context chain so it is not strictly necessary to specify the root node, unless you want the search to stop at current level

dearchap avatar Oct 20 '22 15:10 dearchap