diff --git a/fairwindsops/nova/README.md b/fairwindsops/nova/README.md new file mode 100644 index 0000000..5ddfa32 --- /dev/null +++ b/fairwindsops/nova/README.md @@ -0,0 +1,52 @@ +# POSH nova provider + +## Usage + +### Plugin + +```go +package main + +type Plugin struct { + l log.Logger + kubectl *kubectl.Kubectl + commands command.Commands +} + +func New(l log.Logger) (plugin.Plugin, error) { + var err error + inst := &Plugin{ + l: l, + commands: command.Commands{}, + } + + // ... + + inst.kubectl, err = kubectl.New(l, inst.cache) + if err != nil { + return nil, errors.Wrap(err, "failed to create kubectl") + } + + + // ... + + inst.commands.Add(nova.NewCommand(l, inst.kubectl)) + + // ... + + return inst, nil +} +``` + +### Ownbrew + +To install binary locally, add: + +```yaml +ownbrew: + packages: + # https://github.com/FairwindsOps/nova/releases + - name: nova + tap: foomo/tap/fairwindsops/nova + version: 3.10.1 +``` diff --git a/fairwindsops/nova/command.go b/fairwindsops/nova/command.go new file mode 100644 index 0000000..12c5502 --- /dev/null +++ b/fairwindsops/nova/command.go @@ -0,0 +1,128 @@ +package nova + +import ( + "context" + "sort" + + "github.com/foomo/posh-providers/kubernets/kubectl" + "github.com/foomo/posh/pkg/command/tree" + "github.com/foomo/posh/pkg/log" + "github.com/foomo/posh/pkg/prompt/goprompt" + "github.com/foomo/posh/pkg/readline" + "github.com/foomo/posh/pkg/shell" + "github.com/foomo/posh/pkg/util/suggests" +) + +type ( + Command struct { + l log.Logger + kubectl *kubectl.Kubectl + commandTree tree.Root + } + CommandOption func(*Command) +) + +// ------------------------------------------------------------------------------------------------ +// ~ Constructor +// ------------------------------------------------------------------------------------------------ + +func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, opts ...CommandOption) *Command { + inst := &Command{ + l: l.Named("nova"), + kubectl: kubectl, + } + + inst.commandTree = tree.New(&tree.Node{ + Name: "nova", + Description: "Find outdated or deprecated Helm charts running in your cluster", + Nodes: tree.Nodes{ + { + Name: "cluster", + Values: func(ctx context.Context, r *readline.Readline) []goprompt.Suggest { + var ret []string + for _, cluster := range inst.kubectl.Clusters() { + ret = append(ret, cluster.Name()) + } + sort.Strings(ret) + return suggests.List(ret) + }, + Description: "Cluster name", + Nodes: tree.Nodes{ + { + Name: "find", + Description: "", + Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error { + fs.Default().Bool("containers", false, "Show old container image versions instead of helm chart versions") + fs.Default().Bool("helm", false, "Show old helm chart versions") + fs.Default().Bool("include-all", false, "Show all charts even if no latest version is found") + fs.Default().Bool("show-errored-containers", false, "Show errors encountered when scanning") + fs.Default().Bool("show-non-semver", false, "Show all containers even if they don't follow semver") + fs.Default().Bool("show-old", false, "Only show charts that are not on the latest version") + fs.Default().Bool("wide", false, "Output chart name and namespace") + fs.Default().String("config", "", "Config file to use") + fs.Default().String("format", "table", "An output format (table, json)") + fs.Default().String("namespace", "", "Namespace to look in") + fs.Internal().String("profile", "", "Profile to use") + if r.Args().HasIndex(0) { + if err := fs.Internal().SetValues("profile", inst.kubectl.Cluster(r.Args().At(0)).Profiles(ctx)...); err != nil { + return err + } + } + return nil + }, + Execute: inst.execute, + }, + }, + }, + }, + }) + + return inst +} + +// ------------------------------------------------------------------------------------------------ +// ~ Public methods +// ------------------------------------------------------------------------------------------------ + +func (c *Command) Name() string { + return c.commandTree.Node().Name +} + +func (c *Command) Description() string { + return c.commandTree.Node().Description +} + +func (c *Command) Complete(ctx context.Context, r *readline.Readline) []goprompt.Suggest { + return c.commandTree.Complete(ctx, r) +} + +func (c *Command) Execute(ctx context.Context, r *readline.Readline) error { + return c.commandTree.Execute(ctx, r) +} + +func (c *Command) Help(ctx context.Context, r *readline.Readline) string { + return c.commandTree.Help(ctx, r) +} + +// ------------------------------------------------------------------------------------------------ +// ~ Private methods +// ------------------------------------------------------------------------------------------------ + +func (c *Command) execute(ctx context.Context, r *readline.Readline) error { + fs := r.FlagSets().Default() + ifs := r.FlagSets().Internal() + cluster, args := c.kubectl.Cluster(r.Args().At(0)), r.Args()[1:] + + profile, err := ifs.GetString("profile") + if err != nil { + return err + } + + return shell.New(ctx, c.l, "nova"). + Args(args...). + Args(fs.Visited().Args()...). + Args(r.AdditionalArgs()...). + Args(r.AdditionalFlags()...). + Env(cluster.Env(profile)). + Run() +} diff --git a/fairwindsops/pluto/README.md b/fairwindsops/pluto/README.md new file mode 100644 index 0000000..589ddaa --- /dev/null +++ b/fairwindsops/pluto/README.md @@ -0,0 +1,52 @@ +# POSH pluto provider + +## Usage + +### Plugin + +```go +package main + +type Plugin struct { + l log.Logger + kubectl *kubectl.Kubectl + commands command.Commands +} + +func New(l log.Logger) (plugin.Plugin, error) { + var err error + inst := &Plugin{ + l: l, + commands: command.Commands{}, + } + + // ... + + inst.kubectl, err = kubectl.New(l, inst.cache) + if err != nil { + return nil, errors.Wrap(err, "failed to create kubectl") + } + + + // ... + + inst.commands.Add(pluto.NewCommand(l, inst.kubectl)) + + // ... + + return inst, nil +} +``` + +### Ownbrew + +To install binary locally, add: + +```yaml +ownbrew: + packages: + # https://github.com/FairwindsOps/pluto/releases + - name: pluto + tap: foomo/tap/fairwindsops/pluto + version: 5.20.2 +``` diff --git a/fairwindsops/pluto/command.go b/fairwindsops/pluto/command.go new file mode 100644 index 0000000..0ecf3b3 --- /dev/null +++ b/fairwindsops/pluto/command.go @@ -0,0 +1,124 @@ +package pluto + +import ( + "context" + "sort" + + "github.com/foomo/posh-providers/kubernets/kubectl" + "github.com/foomo/posh/pkg/command/tree" + "github.com/foomo/posh/pkg/log" + "github.com/foomo/posh/pkg/prompt/goprompt" + "github.com/foomo/posh/pkg/readline" + "github.com/foomo/posh/pkg/shell" + "github.com/foomo/posh/pkg/util/suggests" +) + +type ( + Command struct { + l log.Logger + kubectl *kubectl.Kubectl + commandTree tree.Root + } + CommandOption func(*Command) +) + +// ------------------------------------------------------------------------------------------------ +// ~ Constructor +// ------------------------------------------------------------------------------------------------ + +func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, opts ...CommandOption) *Command { + inst := &Command{ + l: l.Named("pluto"), + kubectl: kubectl, + } + + inst.commandTree = tree.New(&tree.Node{ + Name: "pluto", + Description: "Find Kubernetes resources that have been deprecated", + Nodes: tree.Nodes{ + { + Name: "cluster", + Values: func(ctx context.Context, r *readline.Readline) []goprompt.Suggest { + var ret []string + for _, cluster := range inst.kubectl.Clusters() { + ret = append(ret, cluster.Name()) + } + sort.Strings(ret) + return suggests.List(ret) + }, + Description: "Cluster name", + Nodes: tree.Nodes{ + { + Name: "detect", + Description: "", + Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error { + fs.Default().Bool("only-show-removed", false, "Only display the apiVersions that have been removed in the target version") + fs.Default().String("output", "wide", "The output format to use. (normal|wide|custom|json|yaml|markdown|csv)") + fs.Internal().String("profile", "", "Profile to use") + if r.Args().HasIndex(0) { + if err := fs.Internal().SetValues("profile", inst.kubectl.Cluster(r.Args().At(0)).Profiles(ctx)...); err != nil { + return err + } + } + return nil + }, + Execute: inst.detect, + }, + }, + }, + }, + }) + + return inst +} + +// ------------------------------------------------------------------------------------------------ +// ~ Public methods +// ------------------------------------------------------------------------------------------------ + +func (c *Command) Name() string { + return c.commandTree.Node().Name +} + +func (c *Command) Description() string { + return c.commandTree.Node().Description +} + +func (c *Command) Complete(ctx context.Context, r *readline.Readline) []goprompt.Suggest { + return c.commandTree.Complete(ctx, r) +} + +func (c *Command) Execute(ctx context.Context, r *readline.Readline) error { + return c.commandTree.Execute(ctx, r) +} + +func (c *Command) Help(ctx context.Context, r *readline.Readline) string { + return c.commandTree.Help(ctx, r) +} + +// ------------------------------------------------------------------------------------------------ +// ~ Private methods +// ------------------------------------------------------------------------------------------------ + +func (c *Command) detect(ctx context.Context, r *readline.Readline) error { + fs := r.FlagSets().Default() + ifs := r.FlagSets().Internal() + cluster, args := c.kubectl.Cluster(r.Args().At(0)), r.Args()[2:] + + profile, err := ifs.GetString("profile") + if err != nil { + return err + } + + return shell.New(ctx, c.l, "pluto", "detect-all-in-cluster"). + Args(args...). + Args("--ignore-removals"). + Args("--ignore-deprecations"). + Args("--ignore-unavailable-replacements"). + Args(fs.Visited().Args()...). + Args(r.AdditionalArgs()...). + Args(r.AdditionalFlags()...). + Args("2>/dev/null"). + Env(cluster.Env(profile)). + Run() +} diff --git a/grafana/k6/command.go b/grafana/k6/command.go new file mode 100644 index 0000000..209bfed --- /dev/null +++ b/grafana/k6/command.go @@ -0,0 +1,149 @@ +package k6 + +import ( + "context" + "fmt" + "os" + "path" + "strings" + + "github.com/foomo/posh-providers/onepassword" + "github.com/foomo/posh/pkg/command/tree" + "github.com/foomo/posh/pkg/log" + "github.com/foomo/posh/pkg/prompt/goprompt" + "github.com/foomo/posh/pkg/readline" + "github.com/foomo/posh/pkg/shell" + "github.com/foomo/posh/pkg/util/suggests" + "github.com/spf13/viper" +) + +type ( + Command struct { + l log.Logger + op *onepassword.OnePassword + name string + cfg Config + configKey string + commandTree tree.Root + } + CommandOption func(*Command) +) + +// ------------------------------------------------------------------------------------------------ +// ~ Options +// ------------------------------------------------------------------------------------------------ + +func CommandWithName(v string) CommandOption { + return func(o *Command) { + o.name = v + } +} + +func CommandWithConfigKey(v string) CommandOption { + return func(o *Command) { + o.name = v + } +} + +// ------------------------------------------------------------------------------------------------ +// ~ Constructor +// ------------------------------------------------------------------------------------------------ + +func NewCommand(l log.Logger, op *onepassword.OnePassword, opts ...CommandOption) (*Command, error) { + inst := &Command{ + l: l.Named("k6"), + name: "k6", + configKey: "k6", + op: op, + } + for _, opt := range opts { + if opt != nil { + opt(inst) + } + } + + if err := viper.UnmarshalKey(inst.configKey, &inst.cfg); err != nil { + return nil, err + } + + if err := os.MkdirAll(inst.cfg.Path, 0700); err != nil { + return nil, err + } + + inst.commandTree = tree.New(&tree.Node{ + Name: inst.name, + Description: "Run k6", + Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error { + fs.Default().Bool("debug", false, "show debug output") + return nil + }, + Args: tree.Args{ + { + Name: "env", + Description: "Env name", + Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest { + return suggests.List(inst.cfg.EnvNames()) + }, + }, + { + Name: "scenario", + Description: "Scenario name", + Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest { + scenarios, err := inst.cfg.Scenarios() + if err != nil { + return nil + } + return suggests.List(scenarios) + }, + }, + }, + Execute: inst.execute, + }) + + return inst, nil +} + +// ------------------------------------------------------------------------------------------------ +// ~ Public methods +// ------------------------------------------------------------------------------------------------ + +func (c *Command) Name() string { + return c.commandTree.Node().Name +} + +func (c *Command) Description() string { + return c.commandTree.Node().Description +} + +func (c *Command) Complete(ctx context.Context, r *readline.Readline) []goprompt.Suggest { + return c.commandTree.Complete(ctx, r) +} + +func (c *Command) Execute(ctx context.Context, r *readline.Readline) error { + return c.commandTree.Execute(ctx, r) +} + +func (c *Command) Help(ctx context.Context, r *readline.Readline) string { + return c.commandTree.Help(ctx, r) +} + +// ------------------------------------------------------------------------------------------------ +// ~ Private methods +// ------------------------------------------------------------------------------------------------ + +func (c *Command) execute(ctx context.Context, r *readline.Readline) error { + fs := r.FlagSets().Default() + env, scenario := r.Args().At(0), r.Args().At(1) + + var envs []string + for k, v := range c.cfg.Env(env) { + envs = append(envs, fmt.Sprintf("%s=%s", strings.ToUpper(k), v)) + } + + return shell.New(ctx, c.l, "k6", "run", "--out", "web-dashboard", path.Join(c.cfg.Path, scenario)). + Args(fs.Visited().Args()...). + Args(r.AdditionalArgs()...). + Args(r.AdditionalFlags()...). + Env(envs...). + Run() +} diff --git a/grafana/k6/config.go b/grafana/k6/config.go new file mode 100644 index 0000000..1206a44 --- /dev/null +++ b/grafana/k6/config.go @@ -0,0 +1,39 @@ +package k6 + +import ( + "os" + "sort" + "strings" + + "github.com/foomo/posh/pkg/env" + "github.com/samber/lo" +) + +type Config struct { + Path string `yaml:"path"` + Envs map[string]Env `yaml:"envs"` +} + +func (c Config) Env(name string) Env { + return c.Envs[name] +} + +func (c Config) EnvNames() []string { + ret := lo.Keys(c.Envs) + sort.Strings(ret) + return ret +} + +func (c Config) Scenarios() ([]string, error) { + entries, err := os.ReadDir(env.Path(c.Path)) + if err != nil { + return nil, err + } + var ret []string + for _, e := range entries { + if !e.IsDir() && strings.HasSuffix(e.Name(), ".js") { + ret = append(ret, e.Name()) + } + } + return ret, nil +} diff --git a/grafana/k6/env.go b/grafana/k6/env.go new file mode 100644 index 0000000..aa7e45a --- /dev/null +++ b/grafana/k6/env.go @@ -0,0 +1,3 @@ +package k6 + +type Env map[string]string