Skip to content

Commit

Permalink
Package List (Parameters | Tasks | Plans) (#1223)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Schlicht <jan.schlicht+gh@gmail.com>
Signed-off-by: Ken Sipe <kensipe@gmail.com>
  • Loading branch information
kensipe and nfnt committed Jan 7, 2020
1 parent 60b0f19 commit 52ecf33
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 70 deletions.
2 changes: 1 addition & 1 deletion pkg/kudoctl/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ provide a list of parameters from a remote operator given a url or repository al
`

const packageExamples = ` kubectl kudo package create [operator folder]
kubectl kudo package params list [operator]
kubectl kudo package list parameters [operator]
kubectl kudo package verify [operator]
kubectl kudo package add [subcommand]
`
Expand Down
78 changes: 78 additions & 0 deletions pkg/kudoctl/cmd/package_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cmd

import (
"fmt"
"io"

"github.com/spf13/afero"
"github.com/spf13/cobra"

"github.com/kudobuilder/kudo/pkg/kudoctl/clog"
"github.com/kudobuilder/kudo/pkg/kudoctl/env"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages"
pkgresolver "github.com/kudobuilder/kudo/pkg/kudoctl/packages/resolver"
"github.com/kudobuilder/kudo/pkg/kudoctl/util/repo"
)

const packageListDesc = `
This command consists of multiple sub-commands to interact with KUDO packages. These commands are used in the listing
of an operator details such as parameters, tasks or plans.
For list commands, the argument passed represents an operator. That representation can be:
- name of operator in the repository
- url to the operator package (tgz file)
- local operator package
- local operator folder
`

const packageListExamples = ` # show list of parameters from local operator folder
kubectl kudo package list parameters local-folder
# show list of parameters from zookeeper (where zookeeper is name of package in KUDO repository)
kubectl kudo package list parameters zookeeper
# show list of tasks from local operator folder
kubectl kudo package list tasks local-folder
# show list of tasks from zookeeper (where zookeeper is name of package in KUDO repository)
kubectl kudo package list tasks zookeeper
# show list of plans from local operator folder
kubectl kudo package list plans local-folder
# show plans from zookeeper (where zookeeper is name of package in KUDO repository)
kubectl kudo package list plans zookeeper
`

// newPackageParamsCmd for repo commands such as building a repo index
func newPackageParamsCmd(fs afero.Fs, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "list [FLAGS]",
Short: "list context from an operator package",
Long: packageListDesc,
Example: packageListExamples,
}
cmd.AddCommand(newPackageListParamsCmd(fs, out))
cmd.AddCommand(newPackageListPlansCmd(fs, out))
cmd.AddCommand(newPackageListTasksCmd(fs, out))

return cmd
}

// packageDiscovery is used by all list cmds to "discover" the packages
func packageDiscovery(fs afero.Fs, settings *env.Settings, repoName, pathOrName, packageVersion string) (*packages.Package, error) {
repository, err := repo.ClientFromSettings(fs, settings.Home, repoName)
if err != nil {
return nil, fmt.Errorf("could not build operator repository: %w", err)
}
clog.V(3).Printf("repository used %s", repository)

clog.V(3).Printf("getting package pkg files for %v with version: %v", pathOrName, packageVersion)
resolver := pkgresolver.New(repository)
pf, err := resolver.Resolve(pathOrName, packageVersion)
if err != nil {
return nil, fmt.Errorf("failed to resolve package files for operator: %s: %w", pathOrName, err)
}
return pf, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ import (
"github.com/kudobuilder/kudo/pkg/kudoctl/cmd/generate"
"github.com/kudobuilder/kudo/pkg/kudoctl/env"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages"
pkgresolver "github.com/kudobuilder/kudo/pkg/kudoctl/packages/resolver"
"github.com/kudobuilder/kudo/pkg/kudoctl/util/repo"
)

type paramsListCmd struct {
type packageListParamsCmd struct {
fs afero.Fs
out io.Writer
path string
pathOrName string
descriptions bool
namesOnly bool
requiredOnly bool
Expand All @@ -29,37 +27,37 @@ type paramsListCmd struct {
}

const (
pkgParamsExample = `# show parameters from local-folder (where local-folder is a folder in the current directory)
kubectl kudo package params list local-folder
pacakgeListParamsExample = `# show parameters from local-folder (where local-folder is a folder in the current directory)
kubectl kudo package list parameters local-folder
# show parameters from zookeeper (where zookeeper is name of package in KUDO repository)
kubectl kudo package params list zookeeper`
kubectl kudo package list parameters zookeeper`
)

func newParamsListCmd(fs afero.Fs, out io.Writer) *cobra.Command {
list := &paramsListCmd{fs: fs, out: out}
func newPackageListParamsCmd(fs afero.Fs, out io.Writer) *cobra.Command {
list := &packageListParamsCmd{fs: fs, out: out}

cmd := &cobra.Command{
Use: "list [operator]",
Use: "parameters [operator]",
Short: "List operator parameters",
Example: pkgParamsExample,
Example: pacakgeListParamsExample,
RunE: func(cmd *cobra.Command, args []string) error {

path, patherr := generate.OperatorPath(fs)
if patherr != nil {
clog.V(2).Printf("operator path is not relative to execution")
} else {
list.path = path
list.pathOrName = path
}
err := validateOperatorArg(args)
if err != nil && patherr != nil {
return err
}
// if passed in... args take precedence
if err == nil {
list.path = args[0]
list.pathOrName = args[0]
}
return list.run(fs, &Settings)
return list.run(&Settings)
},
}

Expand All @@ -77,32 +75,24 @@ func newParamsListCmd(fs afero.Fs, out io.Writer) *cobra.Command {
// 1. names only using --names. This is based on challenges with other approaches reading really long parameter names
// 2. name, default and required. This is the **default**
// 3. name, default, required, desc.
func (c *paramsListCmd) run(fs afero.Fs, settings *env.Settings) error {
func (c *packageListParamsCmd) run(settings *env.Settings) error {
if !onlyOneSet(c.requiredOnly, c.namesOnly, c.descriptions) {
return fmt.Errorf("only one of the flags 'required', 'names', 'descriptions' can be set")
}
repository, err := repo.ClientFromSettings(fs, settings.Home, c.RepoName)
pf, err := packageDiscovery(c.fs, settings, c.RepoName, c.pathOrName, c.PackageVersion)
if err != nil {
return fmt.Errorf("could not build operator repository: %w", err)
return err
}
clog.V(4).Printf("repository used %s", repository)

clog.V(3).Printf("getting package pkg files for %v with version: %v", c.path, c.PackageVersion)
resolver := pkgresolver.New(repository)
pf, err := resolver.Resolve(c.path, c.PackageVersion)
if err != nil {
return fmt.Errorf("failed to resolve package files for operator: %s: %w", c.path, err)
}

return displayParamsTable(pf.Files, c)
displayParamsTable(pf.Files, c.out, c.requiredOnly, c.namesOnly, c.descriptions)
return nil
}

func displayParamsTable(pf *packages.Files, cmd *paramsListCmd) error {
func displayParamsTable(pf *packages.Files, out io.Writer, printRequired, printNames, printDesc bool) {
sort.Sort(pf.Params.Parameters)
table := uitable.New()
tValue := true
// required
if cmd.requiredOnly {
if printRequired {
table.AddRow("Name")
found := false
for _, p := range pf.Params.Parameters {
Expand All @@ -112,24 +102,21 @@ func displayParamsTable(pf *packages.Files, cmd *paramsListCmd) error {
}
}
if found {
fmt.Fprintln(cmd.out, table)
fmt.Fprintln(out, table)
} else {
fmt.Fprintf(cmd.out, "no required parameters without default values found\n")
fmt.Fprintf(out, "no required parameters without default values found\n")
}
return nil
}
// names only
if cmd.namesOnly {
if printNames {
table.AddRow("Name")
for _, p := range pf.Params.Parameters {
table.AddRow(p.Name)
}
fmt.Fprintln(cmd.out, table)
return nil
fmt.Fprintln(out, table)
}
table.MaxColWidth = 35
table.Wrap = true
if cmd.descriptions {
if printDesc {
table.AddRow("Name", "Default", "Required", "Descriptions")

} else {
Expand All @@ -141,14 +128,13 @@ func displayParamsTable(pf *packages.Files, cmd *paramsListCmd) error {
if p.Default != nil {
pDefault = *p.Default
}
if cmd.descriptions {
if printDesc {
table.AddRow(p.Name, pDefault, *p.Required, p.Description)
} else {
table.AddRow(p.Name, pDefault, *p.Required)
}
}
fmt.Fprintln(cmd.out, table)
return nil
fmt.Fprintln(out, table)
}

func onlyOneSet(b bool, b2 bool, b3 bool) bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
func TestParamsLis(t *testing.T) {
file := "params-list"
out := &bytes.Buffer{}
cmd := newParamsListCmd(fs, out)
cmd := newPackageListParamsCmd(fs, out)
if err := cmd.RunE(cmd, []string{"../packages/testdata/zk.tgz"}); err != nil {
t.Fatal(err)
}
Expand Down
129 changes: 129 additions & 0 deletions pkg/kudoctl/cmd/package_list_plans.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package cmd

import (
"fmt"
"io"
"sort"

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/thoas/go-funk"
"github.com/xlab/treeprint"

"github.com/kudobuilder/kudo/pkg/engine/task"
"github.com/kudobuilder/kudo/pkg/kudoctl/env"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages"
)

type packageListPlansCmd struct {
fs afero.Fs
out io.Writer
pathOrName string
RepoName string
PackageVersion string
WithTasksResources bool
}

const (
packageListPlansExample = ` # show plans from local-folder (where local-folder is a folder in the current directory)
kubectl kudo package list plans local-folder
# show plans from zookeeper (where zookeeper is name of package in KUDO repository)
kubectl kudo package list plans zookeeper`
)

func newPackageListPlansCmd(fs afero.Fs, out io.Writer) *cobra.Command {
lc := &packageListPlansCmd{fs: fs, out: out}

cmd := &cobra.Command{
Use: "plans [operator]",
Short: "List operator plans",
Example: packageListPlansExample,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateOperatorArg(args); err != nil {
return err
}
lc.pathOrName = args[0]
return lc.run(&Settings)
},
}

f := cmd.Flags()
f.StringVar(&lc.RepoName, "repo", "", "Name of repository configuration to use. (default defined by context)")
f.StringVar(&lc.PackageVersion, "version", "", "A specific package version on the official GitHub repo. (default to the most recent)")
f.BoolVarP(&lc.WithTasksResources, "with-tasks", "t", false, "Display task resources with plans")

return cmd
}

func (c *packageListPlansCmd) run(settings *env.Settings) error {
pf, err := packageDiscovery(c.fs, settings, c.RepoName, c.pathOrName, c.PackageVersion)
if err != nil {
return err
}

displayPlanTable(pf.Files, c.WithTasksResources, c.out)
return nil
}

func displayPlanTable(pf *packages.Files, withTasks bool, out io.Writer) {
tree := treeprint.New()
planNames := sortedPlanNames(pf)
tree.SetValue("plans")
for _, name := range planNames {
plan := pf.Operator.Plans[name]
pNode := tree.AddBranch(fmt.Sprintf("%s (%s)", name, plan.Strategy))

for _, phase := range plan.Phases {
phNode := pNode.AddMetaBranch("phase", fmt.Sprintf("%s (%s)", phase.Name, phase.Strategy))
for _, step := range phase.Steps {
sNode := phNode.AddMetaBranch("step", step.Name)
for _, taskName := range step.Tasks {
if withTasks {
addTaskNodeWithResources(sNode, taskName, pf)
} else {
sNode.AddNode(taskName)
}
}
}
}
}

if len(pf.Operator.Plans) == 0 {
fmt.Fprintf(out, "no plans found\n")
} else {
fmt.Fprintln(out, tree.String())
}
}

func sortedPlanNames(pf *packages.Files) []string {
planNames := funk.Keys(pf.Operator.Plans).([]string)
sort.Strings(planNames)
return planNames
}

func addTaskNodeWithResources(sNode treeprint.Tree, taskName string, pf *packages.Files) {
for _, t := range pf.Operator.Tasks {
if t.Name == taskName {
switch t.Kind {
case task.ApplyTaskKind:
tNode := sNode.AddMetaBranch("apply", taskName)
for _, resource := range t.Spec.Resources {
tNode.AddNode(resource)
}
case task.DeleteTaskKind:
tNode := sNode.AddMetaBranch("delete", taskName)
for _, resource := range t.Spec.Resources {
tNode.AddNode(resource)
}
case task.PipeTaskKind:
tNode := sNode.AddMetaBranch("pipe", taskName)
tNode.AddNode(t.Spec.Pod)
case task.DummyTaskKind:
sNode.AddMetaBranch("dummy", taskName)
default:
sNode.AddMetaBranch("unknown", taskName)
}
}
}
}
Loading

0 comments on commit 52ecf33

Please sign in to comment.