From 7d37c56d9d91336954ae4ebcadb255c35ae41348 Mon Sep 17 00:00:00 2001 From: Thomas Guettler Date: Thu, 14 Mar 2024 14:35:30 +0100 Subject: [PATCH] :sparkles: Support calling provider plugins. Signed-off-by: Thomas Guettler --- .gitignore | 1 + Makefile | 1 + csctldocker/csctldocker_main.go | 69 ++++++++++++++++++ docs/design.md | 4 +- pkg/clusterstack/config.go | 6 +- pkg/cmd/create.go | 14 +++- pkg/cmd/version.go | 4 +- pkg/github/github.go | 16 +++++ pkg/providerplugin/providerplugin.go | 71 +++++++++++++++++++ tests/cluster-stacks/docker/ferrol/csctl.yaml | 2 + 10 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 csctldocker/csctldocker_main.go create mode 100644 pkg/providerplugin/providerplugin.go diff --git a/.gitignore b/.gitignore index ae17e243..b70c4600 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,6 @@ temp # build and release dist csctl +csctl-docker tmp/ releases/ diff --git a/Makefile b/Makefile index ab8784cc..e50da5ea 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,7 @@ clean: ## cleans the csctl binary .PHONY: build build: # build the csctl binary go build -ldflags "$(LDFLAGS)" -o csctl main.go + go build -o csctl-docker csctldocker/csctldocker_main.go .PHONY: lint-golang lint-golang: ## Lint Golang codebase diff --git a/csctldocker/csctldocker_main.go b/csctldocker/csctldocker_main.go new file mode 100644 index 00000000..e829aa9b --- /dev/null +++ b/csctldocker/csctldocker_main.go @@ -0,0 +1,69 @@ +/* +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 csctl. You can use that code +// to create a real csctl plugin. +// You can implement the "create-node-images" command to create node images during +// a `csctl create` call. +package main + +import ( + "fmt" + "os" + + csctlclusterstack "github.com/SovereignCloudStack/csctl/pkg/clusterstack" +) + +const provider = "docker" + +func usage() { + fmt.Printf(`%s create-node-images cluster-stack-directory cluster-stack-release-directory +This command is a csctl plugin. + +https://github.com/SovereignCloudStack/csctl +`, 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 := csctlclusterstack.GetCsctlConfig(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.Printf("..... pretending to read config: %s\n", config.Config.Provider.Config["dummyKey"]) + fmt.Printf("..... pretending to do heavy work (creating node images) ...\n") +} diff --git a/docs/design.md b/docs/design.md index 7d338bf8..598bdcaf 100644 --- a/docs/design.md +++ b/docs/design.md @@ -133,8 +133,8 @@ help print a overview of available flags etc. subcommands for provider: install Installs a cluster-stack-release-provider at a version -installed Lists installed csmtcl release providers -remove removes a csmtcl release provider at a version `csmtcl provider remove docker ` +installed Lists installed csctl release providers +remove removes a csctl release provider at a version `csctl provider remove docker ` ``` ## Modes diff --git a/pkg/clusterstack/config.go b/pkg/clusterstack/config.go index e887bf69..e3c45d14 100644 --- a/pkg/clusterstack/config.go +++ b/pkg/clusterstack/config.go @@ -37,9 +37,9 @@ type CsctlConfig 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 768e477d..48447d8e 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -27,6 +27,7 @@ import ( "github.com/SovereignCloudStack/csctl/pkg/github" "github.com/SovereignCloudStack/csctl/pkg/github/client" "github.com/SovereignCloudStack/csctl/pkg/hash" + "github.com/SovereignCloudStack/csctl/pkg/providerplugin" "github.com/SovereignCloudStack/csctl/pkg/template" "github.com/spf13/cobra" "gopkg.in/yaml.v3" @@ -91,6 +92,11 @@ func GetCreateOptions(ctx context.Context, clusterStackPath string) (*CreateOpti createOption.ClusterStackPath = clusterStackPath createOption.Config = config + _, _, err = providerplugin.GetProviderExecutable(&config) + if err != nil { + return createOption, fmt.Errorf("providerplugin.GetProviderExecutable(&config) failed: %w", err) + } + currentHash, err := hash.GetHash(clusterStackPath) if err != nil { return nil, fmt.Errorf("failed to get hash: %w", err) @@ -109,7 +115,7 @@ func GetCreateOptions(ctx context.Context, clusterStackPath string) (*CreateOpti return nil, fmt.Errorf("failed to create new github client: %w", err) } - // update the metadata kubernetes version with the csmctl.yaml config + // update the metadata kubernetes version with the csctl.yaml config createOption.Metadata.Versions.Kubernetes = config.Config.KubernetesVersion latestRepoRelease, err := github.GetLatestReleaseFromRemoteRepository(ctx, mode, &config, gc) @@ -189,7 +195,7 @@ func (c *CreateOptions) generateRelease() error { if err := os.MkdirAll(c.ClusterStackReleaseDir, os.ModePerm); err != nil { return fmt.Errorf("failed to create output directory: %w", err) } - + fmt.Printf("Creating output in %s\n", c.ClusterStackReleaseDir) // Write the current hash hashJSONData, err := json.MarshalIndent(c.CurrentReleaseHash, "", " ") if err != nil { @@ -244,5 +250,9 @@ func (c *CreateOptions) generateRelease() error { return fmt.Errorf("failed to write metadata: %w", err) } + err = providerplugin.CreateNodeImages(&c.Config, c.ClusterStackPath, c.ClusterStackReleaseDir) + if err != nil { + return fmt.Errorf("providerplugin.CreateNodeImages() failed: %w", err) + } return nil } diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index 3fd03f59..677da409 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -31,12 +31,12 @@ var ( var versionCmd = &cobra.Command{ Use: "version", - Short: "prints the latest version of csmctl", + Short: "prints the latest version of csctl", Run: printVersion, SilenceUsage: true, } func printVersion(_ *cobra.Command, _ []string) { - fmt.Println("csmctl version:", Version) + fmt.Println("csctl version:", Version) fmt.Println("commit:", Commit) } diff --git a/pkg/github/github.go b/pkg/github/github.go index 0e4fabae..5c30fcf7 100644 --- a/pkg/github/github.go +++ b/pkg/github/github.go @@ -1,3 +1,19 @@ +/* +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 github import ( diff --git a/pkg/providerplugin/providerplugin.go b/pkg/providerplugin/providerplugin.go new file mode 100644 index 00000000..81d0bafe --- /dev/null +++ b/pkg/providerplugin/providerplugin.go @@ -0,0 +1,71 @@ +/* +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 csctl plugin. +package providerplugin + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/SovereignCloudStack/csctl/pkg/clusterstack" +) + +// GetProviderExecutable returns the path to the provider plugin (like "csctl-docker"). +// If there is not "config" for the provider in csctl.yaml, then "needed" is false and "path" is the empty string. +func GetProviderExecutable(config *clusterstack.CsctlConfig) (needed bool, path string, err error) { + if len(config.Config.Provider.Config) == 0 { + return false, "", nil + } + pluginName := "csctl-" + config.Config.Provider.Type + _, err = os.Stat(pluginName) + if err == nil { + path, err := filepath.Abs(pluginName) + if err != nil { + return false, "", fmt.Errorf("filepath.Abs(%q) failed: %w", pluginName, err) + } + return true, path, nil + } + path, err = exec.LookPath(pluginName) + if err != nil { + return false, "", fmt.Errorf("could not find plugin %s in $PATH or current working directory", pluginName) + } + return true, path, nil +} + +// CreateNodeImages calls the provider plugin command to create nodes images. +func CreateNodeImages(config *clusterstack.CsctlConfig, clusterStackPath, clusterStackReleaseDir string) error { + needed, path, err := GetProviderExecutable(config) + if err != nil { + return err + } + if !needed { + fmt.Printf("No provider specifig configuration in csctl.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 + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return fmt.Errorf("cmd.Run() failed: %w", err) + } + return nil +} diff --git a/tests/cluster-stacks/docker/ferrol/csctl.yaml b/tests/cluster-stacks/docker/ferrol/csctl.yaml index 592fd44c..b4f914ed 100644 --- a/tests/cluster-stacks/docker/ferrol/csctl.yaml +++ b/tests/cluster-stacks/docker/ferrol/csctl.yaml @@ -5,3 +5,5 @@ config: provider: type: docker apiVersion: docker.csctl.clusterstack.x-k8s.io/v1alpha1 + config: + dummyKey: dummyValue