Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config management with viper #395

Merged
merged 2 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,18 @@ import (
"github.com/openconfig/kne/deploy"
"github.com/openconfig/kne/load"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
log "k8s.io/klog/v2"
)

var progress bool

func New() *cobra.Command {
deployCmd := &cobra.Command{
Use: "deploy <deployment yaml>",
Short: "Deploy cluster.",
RunE: deployFn,
}
deployCmd.Flags().BoolVar(&progress, "progress", false, "Display progress of container bringup")
deployCmd.Flags().Bool("progress", false, "Display progress of container bringup")
return deployCmd
}

Expand Down Expand Up @@ -72,10 +71,7 @@ func newDeployment(cfgPath string, testing bool) (*deploy.Deployment, error) {
return nil, err
}
c.IgnoreMissingFiles = testing

cfg := deploy.Deployment{
Progress: progress,
}
cfg := deploy.Deployment{}
if err := c.Decode(&cfg); err != nil {
return nil, err
}
Expand All @@ -101,15 +97,12 @@ func deployFn(cmd *cobra.Command, args []string) error {
if _, err := exec.LookPath("kubectl"); err != nil {
return fmt.Errorf("install kubectl before running deploy: %v", err)
}
kubecfg, err := cmd.Flags().GetString("kubecfg")
if err != nil {
return err
}
d, err := newDeployment(args[0], false)
if err != nil {
return err
}
if err := d.Deploy(cmd.Context(), kubecfg); err != nil {
d.Progress = viper.GetBool("progress")
if err := d.Deploy(cmd.Context(), viper.GetString("kubecfg")); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the GetString returns ""? It would be nice if this (and possibly other) values could be sanity checked prior to running these functions as then you don't have to worry about checking the values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the same behavior as the current implementation, if the flag was the empty string then the function call would handle it as before. in the case of kubecfg the empty string is completely valid as an argument if the user wants to fallback to the in cluster config.

return err
}
log.Infof("Deployment complete, ready for topology")
Expand Down
86 changes: 52 additions & 34 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,58 @@
package cmd

import (
"context"
"fmt"
"os"
"path/filepath"
"time"

"github.com/kr/pretty"
"github.com/openconfig/kne/cmd/deploy"
"github.com/openconfig/kne/cmd/topology"
"github.com/openconfig/kne/topo"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/client-go/util/homedir"
log "k8s.io/klog/v2"
)

var (
kubecfg string
dryrun bool
timeout time.Duration

rootCmd = &cobra.Command{
func New() *cobra.Command {
root := &cobra.Command{
Use: "kne",
Short: "Kubernetes Network Emulation CLI",
Long: `Kubernetes Network Emulation CLI. Works with meshnet to create
layer 2 topology used by containers to layout networks in a k8s
environment.`,
SilenceUsage: true,
}
)
root.SetOut(os.Stdout)
cfgFile := root.PersistentFlags().String("config_file", defaultCfgFile(), "Path to KNE config file")
root.PersistentFlags().String("kubecfg", defaultKubeCfg(), "kubeconfig file")
root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
if *cfgFile == "" {
return nil
}
if _, err := os.Stat(*cfgFile); err == nil {
viper.SetConfigFile(*cfgFile)
if err := viper.ReadInConfig(); err != nil {
return fmt.Errorf("error reading config: %w", err)
}
}
viper.BindPFlags(cmd.Flags())
return nil
}
root.AddCommand(newCreateCmd())
root.AddCommand(newDeleteCmd())
root.AddCommand(newShowCmd())
root.AddCommand(topology.New())
root.AddCommand(deploy.New())
return root
}

// ExecuteContext executes the root command.
func ExecuteContext(ctx context.Context) error {
return rootCmd.ExecuteContext(ctx)
func defaultCfgFile() string {
if home := homedir.HomeDir(); home != "" {
return filepath.Join(home, ".kne", "config.yaml")
}
return ""
}

func defaultKubeCfg() string {
Expand All @@ -60,41 +79,40 @@ func defaultKubeCfg() string {
return ""
}

func init() {
rootCmd.SetOut(os.Stdout)
rootCmd.PersistentFlags().StringVar(&kubecfg, "kubecfg", defaultKubeCfg(), "kubeconfig file")
createCmd.Flags().BoolVar(&dryrun, "dryrun", false, "Generate topology but do not push to k8s")
createCmd.Flags().DurationVar(&timeout, "timeout", 0, "Timeout for pod status enquiry")
rootCmd.AddCommand(createCmd)
rootCmd.AddCommand(deleteCmd)
rootCmd.AddCommand(showCmd)
rootCmd.AddCommand(topology.New())
rootCmd.AddCommand(deploy.New())
}

var (
createCmd = &cobra.Command{
func newCreateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create <topology file>",
Short: "Create Topology",
PreRunE: validateTopology,
RunE: createFn,
ValidArgs: []string{"topology"},
}
deleteCmd = &cobra.Command{
cmd.Flags().Bool("dryrun", false, "Generate topology but do not push to k8s")
cmd.Flags().Duration("timeout", 0, "Timeout for pod status enquiry")
return cmd
}

func newDeleteCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <topology file>",
Short: "Delete Topology",
PreRunE: validateTopology,
RunE: deleteFn,
ValidArgs: []string{"topology"},
}
showCmd = &cobra.Command{
return cmd
}

func newShowCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "show <topology file>",
Short: "Show Topology",
PreRunE: validateTopology,
RunE: showFn,
ValidArgs: []string{"topology"},
}
)
return cmd
}

func validateTopology(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
Expand All @@ -121,22 +139,22 @@ func createFn(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
tm, err := topo.New(topopb, topo.WithKubecfg(kubecfg), topo.WithBasePath(bp))
tm, err := topo.New(topopb, topo.WithKubecfg(viper.GetString("kubecfg")), topo.WithBasePath(bp))
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
if dryrun {
if viper.GetBool("dryrun") {
return nil
}
return tm.Create(cmd.Context(), timeout)
return tm.Create(cmd.Context(), viper.GetDuration("timeout"))
}

func deleteFn(cmd *cobra.Command, args []string) error {
topopb, err := topo.Load(args[0])
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
tm, err := topo.New(topopb, topo.WithKubecfg(kubecfg))
tm, err := topo.New(topopb, topo.WithKubecfg(viper.GetString("kubecfg")))
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
Expand All @@ -148,7 +166,7 @@ func showFn(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
tm, err := topo.New(topopb, topo.WithKubecfg(kubecfg))
tm, err := topo.New(topopb, topo.WithKubecfg(viper.GetString("kubecfg")))
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
Expand Down
43 changes: 11 additions & 32 deletions cmd/topology/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/openconfig/kne/topo"
"github.com/openconfig/kne/topo/node"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/prototext"
Expand Down Expand Up @@ -58,6 +59,8 @@ func New() *cobra.Command {
Short: "reset configuration of device to vendor default (if device not provide reset all nodes)",
RunE: resetCfgFn,
}
resetCfgCmd.Flags().Bool("skip", false, "skip nodes if they are not resetable")
resetCfgCmd.Flags().Bool("push", false, "additionally push orginal topology configuration")
topoCmd := &cobra.Command{
Use: "topology",
Short: "Topology commands.",
Expand All @@ -66,16 +69,12 @@ func New() *cobra.Command {
topoCmd.AddCommand(pushCmd)
topoCmd.AddCommand(serviceCmd)
topoCmd.AddCommand(watchCmd)
resetCfgCmd.Flags().BoolVar(&skipReset, "skip", skipReset, "skip nodes if they are not resetable")
resetCfgCmd.Flags().BoolVar(&pushConfig, "push", pushConfig, "additionally push orginal topology configuration")
topoCmd.AddCommand(resetCfgCmd)
return topoCmd
}

var (
skipReset bool
pushConfig bool
opts []topo.Option
opts []topo.Option
)

func fileRelative(p string) (string, error) {
Expand All @@ -94,11 +93,7 @@ func resetCfgFn(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
s, err := cmd.Flags().GetString("kubecfg")
if err != nil {
return err
}
tOpts := append(opts, topo.WithKubecfg(s))
tOpts := append(opts, topo.WithKubecfg(viper.GetString("kubecfg")))
tm, err := topo.New(topopb, tOpts...)
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
Expand All @@ -113,15 +108,15 @@ func resetCfgFn(cmd *cobra.Command, args []string) error {
default:
return err
case err == nil:
case status.Code(err) == codes.Unimplemented && !skipReset:
case status.Code(err) == codes.Unimplemented && !viper.GetBool("skip"):
return fmt.Errorf("node %q is not a Resetter and --skip not set", name)
case status.Code(err) == codes.Unimplemented:
log.Infof("Skipping node %q not a Resetter", name)
delete(nodes, name)
continue
}
}
if !pushConfig {
if !viper.GetBool("push") {
log.Infof("Finished resetting resettable nodes to vendor default configuration")
return nil
}
Expand Down Expand Up @@ -176,11 +171,7 @@ func pushFn(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
s, err := cmd.Flags().GetString("kubecfg")
if err != nil {
return err
}
tOpts := append(opts, topo.WithKubecfg(s))
tOpts := append(opts, topo.WithKubecfg(viper.GetString("kubecfg")))
tm, err := topo.New(topopb, tOpts...)
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
Expand All @@ -206,11 +197,7 @@ func watchFn(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
s, err := cmd.Flags().GetString("kubecfg")
if err != nil {
return err
}
tOpts := append(opts, topo.WithKubecfg(s))
tOpts := append(opts, topo.WithKubecfg(viper.GetString("kubecfg")))
tm, err := topo.New(topopb, tOpts...)
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
Expand All @@ -226,11 +213,7 @@ func certFn(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
s, err := cmd.Flags().GetString("kubecfg")
if err != nil {
return err
}
tOpts := append(opts, topo.WithKubecfg(s))
tOpts := append(opts, topo.WithKubecfg(viper.GetString("kubecfg")))
tm, err := topo.New(topopb, tOpts...)
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
Expand All @@ -254,11 +237,7 @@ func serviceFn(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
}
s, err := cmd.Flags().GetString("kubecfg")
if err != nil {
return err
}
tOpts := append(opts, topo.WithKubecfg(s))
tOpts := append(opts, topo.WithKubecfg(viper.GetString("kubecfg")))
tm, err := newTopologyManager(topopb, tOpts...)
if err != nil {
return fmt.Errorf("%s: %w", cmd.Use, err)
Expand Down
8 changes: 7 additions & 1 deletion cmd/topology/topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
tpb "github.com/openconfig/kne/proto/topo"
"github.com/openconfig/kne/topo"
"github.com/openconfig/kne/topo/node"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/testing/protocmp"
kfake "k8s.io/client-go/kubernetes/fake"
Expand Down Expand Up @@ -192,7 +194,7 @@ func TestReset(t *testing.T) {
args: []string{"reset", fNoConfig.Name(), "--skip=false"},
wantErr: `node "notresettable1" is not a Resetter`,
}, {
desc: "valid topology no skip",
desc: "valid topology with skip",
args: []string{"reset", fNoConfig.Name(), "--skip"},
}, {
desc: "valid topology no skip nothing to push",
Expand Down Expand Up @@ -231,6 +233,10 @@ func TestReset(t *testing.T) {
opts = origOpts
}()
rCmd.PersistentFlags().String("kubecfg", "", "")
rCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
viper.BindPFlags(cmd.Flags())
return nil
}
buf := bytes.NewBuffer([]byte{})
rCmd.SetOut(buf)
for _, tt := range tests {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
github.com/scrapli/scrapligocfg v1.0.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.15.0
github.com/srl-labs/srl-controller v0.6.0
github.com/srl-labs/srlinux-scrapli v0.6.0
go.universe.tf/metallb v0.13.5
Expand Down Expand Up @@ -106,7 +107,6 @@ require (
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.9.0 // indirect
Expand Down
3 changes: 2 additions & 1 deletion kne_cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func main() {
}
}
flags.Import()
err := cmd.ExecuteContext(context.Background())
root := cmd.New()
err := root.ExecuteContext(context.Background())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could just say cmd.New().ExecuteContext(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

flushLogs()
if err != nil {
os.Exit(1)
Expand Down