From b47f913216c58901d73b5f7a760504dfbc7cab7f Mon Sep 17 00:00:00 2001 From: Thomas Guettler Date: Wed, 24 Jan 2024 14:53:31 +0100 Subject: [PATCH 1/3] :sparkles: Support calling provider plugins. Signed-off-by: Thomas Guettler --- .gitignore | 1 + Makefile | 1 + csmctldocker/csmctldocker_main.go | 48 ++++++++++++++++++++++++++++ pkg/cmd/create.go | 19 +++++++---- pkg/providerplugin/providerplugin.go | 40 +++++++++++++++++++++++ 5 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 csmctldocker/csmctldocker_main.go create mode 100644 pkg/providerplugin/providerplugin.go diff --git a/.gitignore b/.gitignore index c880f366..f5e77faf 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,6 @@ temp # build and release dist csmctl +csmctl-docker tmp/ releases/ diff --git a/Makefile b/Makefile index dde95107..13b43e24 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ export GOBIN := $(abspath $(TOOLS_BIN_DIR)) .PHONY: build build: # build the csmctl binary go build -o csmctl main.go + go build -o csmctl-docker csmctldocker/csmctldocker_main.go .PHONY: lint-golang lint-golang: ## Lint Golang codebase diff --git a/csmctldocker/csmctldocker_main.go b/csmctldocker/csmctldocker_main.go new file mode 100644 index 00000000..29686904 --- /dev/null +++ b/csmctldocker/csmctldocker_main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + + csmctlclusterstack "github.com/SovereignCloudStack/csmctl/pkg/clusterstack" +) + +const provider = "docker" + +func usage() { + fmt.Printf(`%s create-node-images cluster-stack-directory cluster-stack-release-directory +This command is a csmctl plugin. + +https://github.com/SovereignCloudStack/csmctl +`, os.Args[0]) +} + +func main() { + if len(os.Args) != 4 { + usage() + os.Exit(1) + } + if os.Args[1] != "create-node-images" { + usage() + os.Exit(1) + } + clusterStackPath := os.Args[2] + config, err := csmctlclusterstack.GetCsmctlConfig(clusterStackPath) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + if config.Config.Provider.Type != provider { + fmt.Printf("Wrong provider in %s. Expected %s\n", clusterStackPath, provider) + os.Exit(1) + } + releaseDir := os.Args[3] + _, err = os.Stat(releaseDir) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + fmt.Printf("clusterStackPath: %s\n", clusterStackPath) + fmt.Printf("releaseDir: %s\n", releaseDir) + fmt.Println(".... pretending to do heavy work (creating node images) ...") +} diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index 0fcf5f83..16a981b2 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -27,6 +27,7 @@ import ( "github.com/SovereignCloudStack/csmctl/pkg/git" "github.com/SovereignCloudStack/csmctl/pkg/github" "github.com/SovereignCloudStack/csmctl/pkg/hash" + "github.com/SovereignCloudStack/csmctl/pkg/providerplugin" "github.com/SovereignCloudStack/csmctl/pkg/template" "github.com/spf13/cobra" "gopkg.in/yaml.v3" @@ -64,7 +65,8 @@ the cluster stack release in the current directory named "release/". Supported modes are - stable, alpha, beta, hash note - Hash mode takes the last hash of the git commit.`, - RunE: createAction, + RunE: createAction, + SilenceUsage: true, } func init() { @@ -86,6 +88,11 @@ func createAction(_ *cobra.Command, args []string) error { return fmt.Errorf("failed to get config: %w", err) } + _, err = providerplugin.CheckProviderExecutable(config) + if err != nil { + return err + } + // Skip downloading github release for the hash mode. // var latestRepoRelease *string if mode == hashMode { @@ -109,7 +116,7 @@ func createAction(_ *cobra.Command, args []string) error { clusterStackReleaseDir: csrDirName, currentHash: currentHash, } - if err := create.buildPackerAndGenerateRelease(); err != nil { + if err := create.buildNodeImagesAndGenerateRelease(); err != nil { return fmt.Errorf("failed to build packer and generate release: %w", err) } return nil @@ -150,7 +157,7 @@ func createAction(_ *cobra.Command, args []string) error { clusterStackReleaseDir: csrDirName, currentHash: currentHash, } - if err := create.buildPackerAndGenerateRelease(); err != nil { + if err := create.buildNodeImagesAndGenerateRelease(); err != nil { return fmt.Errorf("failed to build packer and generate release: %w", err) } @@ -255,10 +262,10 @@ func handleHashMode(metadata *csmctlclusterstack.MetaData) (*csmctlclusterstack. return metadata, nil } -func (c *CreateOptions) buildPackerAndGenerateRelease() error { +func (c *CreateOptions) buildNodeImagesAndGenerateRelease() error { // Release directory name releaseDirectory := filepath.Join(outputDirectory, c.clusterStackReleaseDir) - + fmt.Printf("Creating output in %s\n", releaseDirectory) // Write the current hash hashJSONData, err := json.MarshalIndent(c.currentHash, "", " ") if err != nil { @@ -287,5 +294,5 @@ func (c *CreateOptions) buildPackerAndGenerateRelease() error { return fmt.Errorf("failed to create package: %w", err) } - return nil + return providerplugin.CreateNodeImages(c.config, c.clusterStackPath, releaseDirectory) } diff --git a/pkg/providerplugin/providerplugin.go b/pkg/providerplugin/providerplugin.go new file mode 100644 index 00000000..1ff01462 --- /dev/null +++ b/pkg/providerplugin/providerplugin.go @@ -0,0 +1,40 @@ +package providerplugin + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/SovereignCloudStack/csmctl/pkg/clusterstack" +) + +func CheckProviderExecutable(config *clusterstack.CsmctlConfig) (path string, err error) { + pluginName := "csmctl-" + config.Config.Provider.Type + _, err = os.Stat(pluginName) + if err == nil { + path, err := filepath.Abs(pluginName) + if err != nil { + return "", err + } + return path, err + } + path, err = exec.LookPath(pluginName) + if err != nil { + return "", fmt.Errorf("could not find plugin %s in $PATH or current working directory", pluginName) + } + return path, nil +} + +func CreateNodeImages(config *clusterstack.CsmctlConfig, clusterStackPath string, clusterStackReleaseDir string) error { + path, err := CheckProviderExecutable(config) + if err != nil { + return err + } + args := []string{"create-node-images", clusterStackPath, clusterStackReleaseDir} + fmt.Printf("Calling Provider Plugin: %s\n", path) + cmd := exec.Command(path, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} From 8784e476a4295e34d9ccebdade2203e99772f7f3 Mon Sep 17 00:00:00 2001 From: Thomas Guettler Date: Thu, 25 Jan 2024 10:45:06 +0100 Subject: [PATCH 2/3] ... rename to GetProviderExecutable and cleanup. --- csmctldocker/csmctldocker_main.go | 20 ++++++++++++++++++++ pkg/cmd/create.go | 2 +- pkg/providerplugin/providerplugin.go | 27 +++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/csmctldocker/csmctldocker_main.go b/csmctldocker/csmctldocker_main.go index 29686904..d01e1344 100644 --- a/csmctldocker/csmctldocker_main.go +++ b/csmctldocker/csmctldocker_main.go @@ -1,3 +1,23 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package main provides a dummy plugin for csmctl. You can use that code +// to create a real csmctl plugin. +// You can implement the "create-node-images" command to create node images during +// a `csmclt create` call. package main import ( diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index 16a981b2..c7424ff9 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -88,7 +88,7 @@ func createAction(_ *cobra.Command, args []string) error { return fmt.Errorf("failed to get config: %w", err) } - _, err = providerplugin.CheckProviderExecutable(config) + _, err = providerplugin.GetProviderExecutable(config) if err != nil { return err } diff --git a/pkg/providerplugin/providerplugin.go b/pkg/providerplugin/providerplugin.go index 1ff01462..da6e08f4 100644 --- a/pkg/providerplugin/providerplugin.go +++ b/pkg/providerplugin/providerplugin.go @@ -1,3 +1,20 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package providerplugin implements calling the provider specific csmctl plugin. package providerplugin import ( @@ -9,7 +26,8 @@ import ( "github.com/SovereignCloudStack/csmctl/pkg/clusterstack" ) -func CheckProviderExecutable(config *clusterstack.CsmctlConfig) (path string, err error) { +// GetProviderExecutable returns the path to the provider plugin (like "csmctl-docker"). +func GetProviderExecutable(config *clusterstack.CsmctlConfig) (path string, err error) { pluginName := "csmctl-" + config.Config.Provider.Type _, err = os.Stat(pluginName) if err == nil { @@ -26,14 +44,15 @@ func CheckProviderExecutable(config *clusterstack.CsmctlConfig) (path string, er return path, nil } -func CreateNodeImages(config *clusterstack.CsmctlConfig, clusterStackPath string, clusterStackReleaseDir string) error { - path, err := CheckProviderExecutable(config) +// CreateNodeImages calls the provider plugin command to create nodes images. +func CreateNodeImages(config *clusterstack.CsmctlConfig, clusterStackPath, clusterStackReleaseDir string) error { + path, err := GetProviderExecutable(config) if err != nil { return err } args := []string{"create-node-images", clusterStackPath, clusterStackReleaseDir} fmt.Printf("Calling Provider Plugin: %s\n", path) - cmd := exec.Command(path, args...) + cmd := exec.Command(path, args...) // #nosec G204 cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() From d3a401afcd191195a3037d37fd622f9605e32dba Mon Sep 17 00:00:00 2001 From: Thomas Guettler Date: Thu, 25 Jan 2024 12:00:08 +0100 Subject: [PATCH 3/3] ... skip calling the plugin of no config in csmctl.yaml. --- pkg/clusterstack/config.go | 6 +++--- pkg/cmd/create.go | 2 +- pkg/providerplugin/providerplugin.go | 20 ++++++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pkg/clusterstack/config.go b/pkg/clusterstack/config.go index 46b4e3e9..12e48711 100644 --- a/pkg/clusterstack/config.go +++ b/pkg/clusterstack/config.go @@ -36,9 +36,9 @@ type CsmctlConfig struct { KubernetesVersion string `yaml:"kubernetesVersion"` ClusterStackName string `yaml:"clusterStackName"` Provider struct { - Type string `yaml:"type"` - APIVersion string `yaml:"apiVersion"` - Config struct{} `yaml:"config"` + Type string `yaml:"type"` + APIVersion string `yaml:"apiVersion"` + Config map[string]interface{} `yaml:"config"` } `yaml:"provider"` } `yaml:"config"` } diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index c7424ff9..7ce5d9a7 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -88,7 +88,7 @@ func createAction(_ *cobra.Command, args []string) error { return fmt.Errorf("failed to get config: %w", err) } - _, err = providerplugin.GetProviderExecutable(config) + _, _, err = providerplugin.GetProviderExecutable(config) if err != nil { return err } diff --git a/pkg/providerplugin/providerplugin.go b/pkg/providerplugin/providerplugin.go index da6e08f4..24b70436 100644 --- a/pkg/providerplugin/providerplugin.go +++ b/pkg/providerplugin/providerplugin.go @@ -27,29 +27,37 @@ import ( ) // GetProviderExecutable returns the path to the provider plugin (like "csmctl-docker"). -func GetProviderExecutable(config *clusterstack.CsmctlConfig) (path string, err error) { +// If there is not "config" for the provider in csmctl.yaml, then needed is false and path is the empty string. +func GetProviderExecutable(config *clusterstack.CsmctlConfig) (needed bool, path string, err error) { + if len(config.Config.Provider.Config) == 0 { + return false, "", nil + } pluginName := "csmctl-" + config.Config.Provider.Type _, err = os.Stat(pluginName) if err == nil { path, err := filepath.Abs(pluginName) if err != nil { - return "", err + return false, "", err } - return path, err + return true, path, nil } path, err = exec.LookPath(pluginName) if err != nil { - return "", fmt.Errorf("could not find plugin %s in $PATH or current working directory", pluginName) + return false, "", fmt.Errorf("could not find plugin %s in $PATH or current working directory", pluginName) } - return path, nil + return true, path, nil } // CreateNodeImages calls the provider plugin command to create nodes images. func CreateNodeImages(config *clusterstack.CsmctlConfig, clusterStackPath, clusterStackReleaseDir string) error { - path, err := GetProviderExecutable(config) + needed, path, err := GetProviderExecutable(config) if err != nil { return err } + if !needed { + fmt.Printf("No provider specifig configuration in csmctl.yaml. No need to call a plugin for provider %q\n", config.Config.Provider.Type) + return nil + } args := []string{"create-node-images", clusterStackPath, clusterStackReleaseDir} fmt.Printf("Calling Provider Plugin: %s\n", path) cmd := exec.Command(path, args...) // #nosec G204