diff --git a/README.md b/README.md index d075fce..23c954c 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ clouds: ```` *Note*: The cloud/project_name is automatically discovered from the current kube context. E.g. a kube context named `i01p015-cluster-admin` leads to a cloud/project_name of `i01p015`. +*Note*: The clouds.yaml file can be created from `.rc` files via the `import-config` sub command. # Usage diff --git a/pkg/cmd/BUILD.bazel b/pkg/cmd/BUILD.bazel index e24901f..aa11f4c 100644 --- a/pkg/cmd/BUILD.bazel +++ b/pkg/cmd/BUILD.bazel @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ + "config_import.go", "kubernetes.go", "lb.go", "openstack.go", diff --git a/pkg/cmd/config_import.go b/pkg/cmd/config_import.go new file mode 100644 index 0000000..f485350 --- /dev/null +++ b/pkg/cmd/config_import.go @@ -0,0 +1,156 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "regexp" + "strings" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +type ImportConfigOptions struct { + configFlags *genericclioptions.ConfigFlags + + fromDir string + openstackConfigFile string + + genericclioptions.IOStreams +} + +var ( + importConfigExample = ` + # import config + export OPENSTACK_CONFIG_FILE=~/clouds.yaml + %[1]s import-config --from-dir ~/openstack/tenants/ +` +) + +func NewCmdImportConfig(streams genericclioptions.IOStreams) *cobra.Command { + o := &ImportConfigOptions{ + configFlags: genericclioptions.NewConfigFlags(), + IOStreams: streams, + } + cmd := &cobra.Command{ + Use: "import-config", + Aliases: []string{"rc"}, + Short: "Import config from OpenStack rc files.", + Example: fmt.Sprintf(importConfigExample, "kubectl os"), + SilenceUsage: true, + RunE: func(c *cobra.Command, args []string) error { + if err := o.Complete(c, args); err != nil { + return err + } + if err := o.Validate(); err != nil { + return err + } + if err := o.Run(); err != nil { + return err + } + return nil + }, + } + cmd.Flags().StringVar(&o.fromDir, "from-dir", o.fromDir, "dir where the rc files are located") + o.configFlags.AddFlags(cmd.Flags()) + return cmd +} + +func (o *ImportConfigOptions) Complete(cmd *cobra.Command, args []string) error { + o.openstackConfigFile = os.Getenv("OPENSTACK_CONFIG_FILE") + return nil +} + +func (o *ImportConfigOptions) Validate() error { + if o.fromDir == "" { + return fmt.Errorf("--from-dir is mandatory") + } + if o.openstackConfigFile == "" { + return fmt.Errorf("env var 'OPENSTACK_CONFIG_FILE' must be set") + } + return nil +} + +func (o *ImportConfigOptions) Run() error { + + files, err := ioutil.ReadDir(o.fromDir) + if err != nil { + return fmt.Errorf("error reading dir %s: %v", o.fromDir, err) + } + + usernameRegEx := regexp.MustCompile("OS_USERNAME='(.*)'") + passwordRegEx := regexp.MustCompile("OS_PASSWORD='(.*)'") + tenantNameRegEx := regexp.MustCompile("OS_TENANT_NAME=['\"](.*)['\"]") + authUrlRegEx := regexp.MustCompile("OS_AUTH_URL='(.*)'") + + clouds := clouds{} + clouds.Clouds = map[string]cloud{} + for _, f := range files { + if strings.HasSuffix(f.Name(), ".creds") { + + fmt.Printf("Importing config from: %q\n", f.Name()) + + content, err := ioutil.ReadFile(path.Join(o.fromDir, f.Name())) + if err != nil { + return fmt.Errorf("error reading cred file %s: %v", path.Join(o.fromDir, f.Name()), err) + } + + usernameMatch := usernameRegEx.FindSubmatch(content) + passwordMatch := passwordRegEx.FindSubmatch(content) + tenantNameMatch := tenantNameRegEx.FindSubmatch(content) + authUrlMatch := authUrlRegEx.FindSubmatch(content) + + if len(usernameMatch) < 1 { + return fmt.Errorf("error matching username regex") + } + if len(passwordMatch) < 1 { + return fmt.Errorf("error matching username regex") + } + if len(tenantNameMatch) < 1 { + return fmt.Errorf("error matching username regex") + } + if len(authUrlMatch) < 1 { + return fmt.Errorf("error matching username regex") + } + + clouds.Clouds[string(tenantNameMatch[1])] = cloud{ + Auth: cloudAuth{ + Username: string(usernameMatch[1]), + Password: string(passwordMatch[1]), + ProjectName: string(tenantNameMatch[1]), + AuthUrl: string(authUrlMatch[1]), + }, + } + } + } + + out, err := yaml.Marshal(clouds) + if err != nil { + return fmt.Errorf("error marshalling couds: %v", err) + } + + fmt.Printf("Writing config to %s\n", o.openstackConfigFile) + + err = ioutil.WriteFile(o.openstackConfigFile, out, os.ModePerm) + if err != nil { + return fmt.Errorf("error writing config file %s: %v", o.openstackConfigFile, err) + } + return nil +} + +type clouds struct { + Clouds map[string]cloud `yaml:"clouds"` +} +type cloud struct { + Auth cloudAuth `yaml:"auth"` +} + +type cloudAuth struct { + AuthUrl string `yaml:"auth_url"` + ProjectName string `yaml:"project_name"` + Username string `yaml:"username"` + Password string `yaml:"password"` +} diff --git a/pkg/cmd/lb.go b/pkg/cmd/lb.go index d70fc52..953a2c9 100644 --- a/pkg/cmd/lb.go +++ b/pkg/cmd/lb.go @@ -30,7 +30,7 @@ type LBOptions struct { var ( lbExample = ` # list lb - %[1] lb + %[1]s lb ` ) diff --git a/pkg/cmd/os.go b/pkg/cmd/os.go index f119ae6..b49d064 100644 --- a/pkg/cmd/os.go +++ b/pkg/cmd/os.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -24,5 +25,6 @@ func NewCmdOpenStack(streams genericclioptions.IOStreams) *cobra.Command { cmd.AddCommand(NewCmdLB(streams)) cmd.AddCommand(NewCmdServer(streams)) cmd.AddCommand(NewCmdVolumes(streams)) + cmd.AddCommand(NewCmdImportConfig(streams)) return cmd } diff --git a/pkg/cmd/server.go b/pkg/cmd/server.go index 74f6326..25c7232 100644 --- a/pkg/cmd/server.go +++ b/pkg/cmd/server.go @@ -26,7 +26,7 @@ type ServerOptions struct { var ( serverExample = ` # list server - %[1] server + %[1]s server ` ) diff --git a/pkg/cmd/volume.go b/pkg/cmd/volume.go index fb8290e..86a90b8 100644 --- a/pkg/cmd/volume.go +++ b/pkg/cmd/volume.go @@ -26,7 +26,7 @@ type VolumesOptions struct { var ( volumesExample = ` # list volumes - %[1] volumes + %[1]s volumes ` ) diff --git a/pkg/util/BUILD.bazel b/pkg/util/BUILD.bazel new file mode 100644 index 0000000..6fdd77d --- /dev/null +++ b/pkg/util/BUILD.bazel @@ -0,0 +1,14 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["util.go"], + importpath = "github.com/sbueringer/kubectl-openstack-plugin/pkg/util", + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["util_test.go"], + embed = [":go_default_library"], +) diff --git a/pkg/util/util.go b/pkg/util/util.go new file mode 100644 index 0000000..c7d8682 --- /dev/null +++ b/pkg/util/util.go @@ -0,0 +1 @@ +package util diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go new file mode 100644 index 0000000..c7d8682 --- /dev/null +++ b/pkg/util/util_test.go @@ -0,0 +1 @@ +package util