refactor main: Move to Kingpin CLI library

Replace urfave/cli with the kingpin[1] library.

It has slightly more sensible argument validation than the other Go libraries.

Additionally I've opted for removing the '-f / --file' flag in favour of positional arguments to commands.

A previous command like `kontemplate template -f somefile.yml` is now just `kontemplate template somefile.yml`. All other arguments remain the same.

[1]: https://github.com/alecthomas/kingpin
This commit is contained in:
Vincent Ambo 2017-05-04 18:32:26 +02:00
parent 32ca64c50a
commit c8a63861ae

222
main.go
View file

@ -8,141 +8,111 @@ import (
"github.com/polydawn/meep" "github.com/polydawn/meep"
"github.com/tazjin/kontemplate/context" "github.com/tazjin/kontemplate/context"
"github.com/tazjin/kontemplate/templater" "github.com/tazjin/kontemplate/templater"
"github.com/urfave/cli" "gopkg.in/alecthomas/kingpin.v2"
) )
type KubeCtlError struct { type KubeCtlError struct {
meep.AllTraits meep.AllTraits
} }
var (
app = kingpin.New("kontemplate", "simple Kubernetes resource templating")
// Global flags
includes = app.Flag("include", "Resource sets to include explicitly").Short('i').Strings()
excludes = app.Flag("exclude", "Resource sets to exclude explicitly").Short('e').Strings()
// Commands
template = app.Command("template", "Template resource sets and print them")
templateFile = template.Arg("file", "Cluster configuration file to use").Required().String()
apply = app.Command("apply", "Template resources and pass to 'kubectl apply'")
applyFile = apply.Arg("file", "Cluster configuration file to use").Required().String()
applyDryRun = apply.Flag("dry-run", "Print remote operations without executing them").Default("false").Bool()
replace = app.Command("replace", "Template resources and pass to 'kubectl replace'")
replaceFile = replace.Arg("file", "Cluster configuration file to use").Required().String()
delete = app.Command("delete", "Template resources and pass to 'kubectl delete'")
deleteFile = delete.Arg("file", "Cluster configuration file to use").Required().String()
create = app.Command("create", "Template resources and pass to 'kubectl create'")
createFile = create.Arg("file", "Cluster configuration file to use").Required().String()
)
func main() { func main() {
app := cli.NewApp() app.HelpFlag.Short('h')
app.Name = "kontemplate" switch kingpin.MustParse(app.Parse(os.Args[1:])) {
app.Usage = "simple Kubernetes resource templating" case template.FullCommand():
app.Version = "v1.0.0-beta1" templateCommand()
app.Commands = []cli.Command{ case apply.FullCommand():
templateCommand(), applyCommand()
applyCommand(),
replaceCommand(),
deleteCommand(),
}
app.Run(os.Args) case replace.FullCommand():
} replaceCommand()
func templateCommand() cli.Command { case delete.FullCommand():
return cli.Command{ deleteCommand()
Name: "template",
Usage: "Interpolate and print templates",
Flags: commonFlags(),
Action: func(c *cli.Context) error {
include := c.StringSlice("include")
exclude := c.StringSlice("exclude")
ctx, err := loadContext(c) case create.FullCommand():
if err != nil { createCommand()
return err
}
resources, err := templater.LoadAndPrepareTemplates(&include, &exclude, ctx)
if err != nil {
return err
}
for _, r := range resources {
fmt.Println(r)
}
return nil
},
} }
} }
func applyCommand() cli.Command { func templateCommand() {
dryRun := false _, resources := loadContextAndResources(templateFile)
return cli.Command{ for _, r := range *resources {
Name: "apply", fmt.Println(r)
Usage: "Interpolate templates and run 'kubectl apply'",
Flags: append(commonFlags(), cli.BoolFlag{
Name: "dry-run",
Usage: "Only print objects that would be sent, without sending them",
Destination: &dryRun,
}),
Action: func(c *cli.Context) error {
include := c.StringSlice("include")
exclude := c.StringSlice("exclude")
ctx, err := loadContext(c)
if err != nil {
return err
}
resources, err := templater.LoadAndPrepareTemplates(&include, &exclude, ctx)
if err != nil {
return err
}
var args []string
if dryRun {
args = []string{"apply", "-f", "-", "--dry-run"}
} else {
args = []string{"apply", "-f", "-"}
}
return runKubectlWithResources(ctx, &args, &resources)
},
} }
} }
func replaceCommand() cli.Command { func applyCommand() {
return cli.Command{ ctx, resources := loadContextAndResources(applyFile)
Name: "replace",
Usage: "Interpolate templates and run 'kubectl replace'",
Flags: commonFlags(),
Action: func(c *cli.Context) error {
include := c.StringSlice("include")
exclude := c.StringSlice("exclude")
ctx, err := loadContext(c)
if err != nil {
return err
}
resources, err := templater.LoadAndPrepareTemplates(&include, &exclude, ctx) var kubectlArgs []string
if err != nil {
return err
}
args := []string{"replace", "--save-config=true", "-f", "-"} if *applyDryRun {
return runKubectlWithResources(ctx, &args, &resources) kubectlArgs = []string{"apply", "-f", "-", "--dry-run"}
}, } else {
kubectlArgs = []string{"apply", "-f", "-"}
} }
runKubectlWithResources(ctx, &kubectlArgs, resources)
} }
func deleteCommand() cli.Command { func replaceCommand() {
return cli.Command{ ctx, resources := loadContextAndResources(replaceFile)
Name: "delete", args := []string{"replace", "--save-config=true", "-f", "-"}
Usage: "Interpolate templates and run 'kubectl delete'", runKubectlWithResources(ctx, &args, resources)
Flags: commonFlags(), }
Action: func(c *cli.Context) error {
include := c.StringSlice("include")
exclude := c.StringSlice("exclude")
ctx, err := loadContext(c) func deleteCommand() {
if err != nil { ctx, resources := loadContextAndResources(deleteFile)
return err args := []string{"delete", "-f", "-"}
} runKubectlWithResources(ctx, &args, resources)
}
resources, err := templater.LoadAndPrepareTemplates(&include, &exclude, ctx) func createCommand() {
if err != nil { ctx, resources := loadContextAndResources(createFile)
return err args := []string{"create", "--save-config=true", "-f", "-"}
} runKubectlWithResources(ctx, &args, resources)
}
args := []string{"delete", "-f", "-"} func loadContextAndResources(file *string) (*context.Context, *[]string) {
return runKubectlWithResources(ctx, &args, &resources) ctx, err := context.LoadContextFromFile(*file)
}, if err != nil {
app.Fatalf("Error loading context: %v\n", err)
} }
resources, err := templater.LoadAndPrepareTemplates(includes, excludes, ctx)
if err != nil {
app.Fatalf("Error templating resource sets: %v\n", err)
}
return ctx, &resources
} }
func runKubectlWithResources(c *context.Context, kubectlArgs *[]string, resources *[]string) error { func runKubectlWithResources(c *context.Context, kubectlArgs *[]string, resources *[]string) error {
@ -171,41 +141,3 @@ func runKubectlWithResources(c *context.Context, kubectlArgs *[]string, resource
return nil return nil
} }
func commonFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "file, f",
Usage: "Cluster configuration file to use",
},
cli.StringSliceFlag{
Name: "include, i",
Usage: "Limit templating to explicitly included resource sets",
},
cli.StringSliceFlag{
Name: "exclude, e",
Usage: "Exclude certain resource sets from templating",
},
}
}
func loadContext(c *cli.Context) (*context.Context, error) {
f := c.String("file")
if f == "" {
return nil, meep.New(
&meep.ErrInvalidParam{
Param: "file",
Reason: "Cluster config file must be specified (-f)",
},
)
}
ctx, err := context.LoadContextFromFile(f)
if err != nil {
return nil, err
}
return ctx, nil
}