diff --git a/pkg/cli/alpha.go b/pkg/cli/alpha.go index 403a024ab59..7f4d1f12891 100644 --- a/pkg/cli/alpha.go +++ b/pkg/cli/alpha.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/spf13/cobra" + "sigs.k8s.io/kubebuilder/v3/pkg/cli/alpha" ) const ( @@ -29,6 +30,7 @@ const ( var alphaCommands = []*cobra.Command{ newAlphaCommand(), + alpha.NewScaffoldCommand(), } func newAlphaCommand() *cobra.Command { diff --git a/pkg/cli/alpha/generate.go b/pkg/cli/alpha/generate.go new file mode 100644 index 00000000000..23c346f3216 --- /dev/null +++ b/pkg/cli/alpha/generate.go @@ -0,0 +1,50 @@ +/* +Copyright 2023 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 alpha + +import ( + "log" + + "github.com/spf13/cobra" + "sigs.k8s.io/kubebuilder/v3/pkg/rescaffold" +) + +// NewScaffoldCommand return a new scaffold command +func NewScaffoldCommand() *cobra.Command { + opts := rescaffold.MigrateOptions{} + scaffoldCmd := &cobra.Command{ + Use: "generate", + Short: "Re-scaffold an existing Kuberbuilder project", + Long: `It's an experimental feature that has the purpose of re-scaffolding the whole project from the scratch +using the current version of KubeBuilder binary available. +# make sure the PROJECT file is in the 'input-dir' argument, the default is the current directory. +$ kubebuilder alpha generate --input-dir="./test" --output-dir="./my-output" +Then we will re-scaffold the project by Kubebuilder in the directory specified by 'output-dir'. + `, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return opts.Validate() + }, + Run: func(cmd *cobra.Command, args []string) { + if err := opts.Rescaffold(); err != nil { + log.Fatalf("Failed to rescaffold %s", err) + } + }, + } + scaffoldCmd.Flags().StringVar(&opts.InputDir, "input-dir", "", + "path to a Kubebuilder project file if not in the current working directory") + scaffoldCmd.Flags().StringVar(&opts.OutputDir, "output-dir", "", + "path to output the scaffolding. defaults a directory in the current working directory") + + return scaffoldCmd +} diff --git a/pkg/rescaffold/migrate.go b/pkg/rescaffold/migrate.go new file mode 100644 index 00000000000..91854c24783 --- /dev/null +++ b/pkg/rescaffold/migrate.go @@ -0,0 +1,107 @@ +/* +Copyright 2023 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 rescaffold + +import ( + "fmt" + "log" + "os" + "os/exec" + + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config/store" + "sigs.k8s.io/kubebuilder/v3/pkg/config/store/yaml" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" +) + +type MigrateOptions struct { + InputDir string + OutputDir string +} + +const DefaultOutputDir = "output-dir" + +func (opts *MigrateOptions) Rescaffold() error { + config := yaml.New(machinery.Filesystem{FS: afero.NewOsFs()}) + if err := config.LoadFrom(opts.InputDir); err != nil { + log.Fatal(err) + } + // create output directory + // nolint: gosec + if err := os.MkdirAll(opts.OutputDir, 0755); err != nil { + log.Fatal(err) + } + // use the new directory to set up the new project + if err := os.Chdir(opts.OutputDir); err != nil { + log.Fatal(err) + } + // init project with plugins + if err := kubebuilderInit(config); err != nil { + log.Fatal(err) + } + return nil +} + +func (opts *MigrateOptions) Validate() error { + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + // get PROJECT path from command args + inputPath, err := getInputPath(cwd, opts.InputDir) + if err != nil { + log.Fatal(err) + } + opts.InputDir = inputPath + // get output path from command args + opts.OutputDir, err = getOutputPath(cwd, opts.OutputDir) + if err != nil { + log.Fatal(err) + } + // check whether the kubebuilder binary is accessible + _, err = exec.LookPath("kubebuilder") + return err +} + +func getInputPath(currentWorkingDirectory string, inputPath string) (string, error) { + if inputPath == "" { + inputPath = currentWorkingDirectory + } + projectPath := fmt.Sprintf("%s/%s", inputPath, yaml.DefaultPath) + if _, err := os.Stat(projectPath); os.IsNotExist(err) { + return "", fmt.Errorf("PROJECT path: %s does not exist. %v", projectPath, err) + } + return projectPath, nil +} + +func getOutputPath(currentWorkingDirectory, outputPath string) (string, error) { + if outputPath == "" { + outputPath = fmt.Sprintf("%s/%s", currentWorkingDirectory, DefaultOutputDir) + } + _, err := os.Stat(outputPath) + if err == nil { + return "", fmt.Errorf("Output path: %s already exists. %v", outputPath, err) + } + if os.IsNotExist(err) { + return outputPath, nil + } + return "", err +} + +func kubebuilderInit(_ store.Store) error { + var args []string + args = append(args, "init") + return util.RunCmd("kubebuilder init", "kubebuilder", args...) +}