sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/rescaffold/migrate.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7 Unless required by applicable law or agreed to in writing, software 8 distributed under the License is distributed on an "AS IS" BASIS, 9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 See the License for the specific language governing permissions and 11 limitations under the License. 12 */ 13 14 package rescaffold 15 16 import ( 17 "errors" 18 "fmt" 19 "os" 20 "os/exec" 21 "strings" 22 23 log "github.com/sirupsen/logrus" 24 25 "github.com/spf13/afero" 26 "sigs.k8s.io/kubebuilder/v3/pkg/config" 27 "sigs.k8s.io/kubebuilder/v3/pkg/config/store" 28 "sigs.k8s.io/kubebuilder/v3/pkg/config/store/yaml" 29 "sigs.k8s.io/kubebuilder/v3/pkg/machinery" 30 "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" 31 "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" 32 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/deploy-image/v1alpha1" 33 ) 34 35 type MigrateOptions struct { 36 InputDir string 37 OutputDir string 38 } 39 40 const DefaultOutputDir = "output-dir" 41 const grafanaPluginKey = "grafana.kubebuilder.io/v1-alpha" 42 43 func (opts *MigrateOptions) Rescaffold() error { 44 config := yaml.New(machinery.Filesystem{FS: afero.NewOsFs()}) 45 if err := config.LoadFrom(fmt.Sprintf("%s/%s", opts.InputDir, yaml.DefaultPath)); err != nil { 46 log.Fatalf("Failed to load PROJECT file %v", err) 47 } 48 // create output directory 49 // nolint: gosec 50 if err := os.MkdirAll(opts.OutputDir, 0755); err != nil { 51 log.Fatalf("Failed to create output directory %v", err) 52 } 53 // use the new directory to set up the new project 54 if err := os.Chdir(opts.OutputDir); err != nil { 55 log.Fatalf("Failed to change the current working directory %v", err) 56 } 57 // init project with plugins 58 if err := kubebuilderInit(config); err != nil { 59 log.Fatalf("Failed to run init subcommand %v", err) 60 } 61 // call edit subcommands to enable or disable multigroup layout 62 if err := kubebuilderEdit(config); err != nil { 63 log.Fatalf("Failed to run edit subcommand %v", err) 64 } 65 // create APIs and Webhooks 66 if err := kubebuilderCreate(config); err != nil { 67 log.Fatalf("Failed to run create API subcommand %v", err) 68 } 69 // plugin specific migration 70 if err := migrateGrafanaPlugin(config, opts.InputDir, opts.OutputDir); err != nil { 71 log.Fatalf("Failed to run grafana plugin migration %v", err) 72 } 73 if err := migrateDeployImagePlugin(config); err != nil { 74 log.Fatalf("Failed to run deploy-image plugin migration %v", err) 75 } 76 return nil 77 } 78 79 func (opts *MigrateOptions) Validate() error { 80 cwd, err := os.Getwd() 81 if err != nil { 82 log.Fatal(err) 83 } 84 // get PROJECT path from command args 85 inputPath, err := getInputPath(cwd, opts.InputDir) 86 if err != nil { 87 log.Fatal(err) 88 } 89 opts.InputDir = inputPath 90 // get output path from command args 91 opts.OutputDir, err = getOutputPath(cwd, opts.OutputDir) 92 if err != nil { 93 log.Fatal(err) 94 } 95 // check whether the kubebuilder binary is accessible 96 _, err = exec.LookPath("kubebuilder") 97 return err 98 } 99 100 func getInputPath(currentWorkingDirectory string, inputPath string) (string, error) { 101 if inputPath == "" { 102 inputPath = currentWorkingDirectory 103 } 104 projectPath := fmt.Sprintf("%s/%s", inputPath, yaml.DefaultPath) 105 if _, err := os.Stat(projectPath); os.IsNotExist(err) { 106 return "", fmt.Errorf("PROJECT path: %s does not exist. %v", projectPath, err) 107 } 108 return inputPath, nil 109 } 110 111 func getOutputPath(currentWorkingDirectory, outputPath string) (string, error) { 112 if outputPath == "" { 113 outputPath = fmt.Sprintf("%s/%s", currentWorkingDirectory, DefaultOutputDir) 114 } 115 _, err := os.Stat(outputPath) 116 if err == nil { 117 return "", fmt.Errorf("Output path: %s already exists. %v", outputPath, err) 118 } 119 if os.IsNotExist(err) { 120 return outputPath, nil 121 } 122 return "", err 123 } 124 125 func kubebuilderInit(store store.Store) error { 126 var args []string 127 args = append(args, "init") 128 args = append(args, getInitArgs(store)...) 129 return util.RunCmd("kubebuilder init", "kubebuilder", args...) 130 } 131 132 func kubebuilderEdit(store store.Store) error { 133 if store.Config().IsMultiGroup() { 134 args := []string{"edit", "--multigroup"} 135 return util.RunCmd("kubebuilder edit", "kubebuilder", args...) 136 } 137 return nil 138 } 139 140 func kubebuilderCreate(store store.Store) error { 141 resources, err := store.Config().GetResources() 142 if err != nil { 143 return err 144 } 145 146 for _, r := range resources { 147 if err = createAPI(r); err != nil { 148 return err 149 } 150 if err = createWebhook(r); err != nil { 151 return err 152 } 153 } 154 155 return nil 156 } 157 158 func migrateGrafanaPlugin(store store.Store, src, des string) error { 159 var grafanaPlugin struct{} 160 err := store.Config().DecodePluginConfig(grafanaPluginKey, grafanaPlugin) 161 // If the grafana plugin is not found, we don't need to migrate 162 if err != nil { 163 if errors.As(err, &config.PluginKeyNotFoundError{}) { 164 log.Info("Grafana plugin is not found, skip the migration") 165 return nil 166 } 167 return fmt.Errorf("failed to decode grafana plugin config %v", err) 168 } 169 err = kubebuilderGrafanaEdit() 170 if err != nil { 171 return err 172 } 173 err = grafanaConfigMigrate(src, des) 174 if err != nil { 175 return err 176 } 177 return kubebuilderGrafanaEdit() 178 } 179 180 func migrateDeployImagePlugin(store store.Store) error { 181 deployImagePlugin := v1alpha1.PluginConfig{} 182 err := store.Config().DecodePluginConfig("deploy-image.go.kubebuilder.io/v1-alpha", &deployImagePlugin) 183 // If the deploy-image plugin is not found, we don't need to migrate 184 if err != nil { 185 if errors.As(err, &config.PluginKeyNotFoundError{}) { 186 log.Printf("deploy-image plugin is not found, skip the migration") 187 return nil 188 } 189 return fmt.Errorf("failed to decode deploy-image plugin config %v", err) 190 } 191 192 for _, r := range deployImagePlugin.Resources { 193 if err = createAPIWithDeployImage(r); err != nil { 194 return err 195 } 196 } 197 return nil 198 } 199 200 func createAPIWithDeployImage(resource v1alpha1.ResourceData) error { 201 var args []string 202 args = append(args, "create") 203 args = append(args, "api") 204 args = append(args, getGVKFlagsFromDeployImage(resource)...) 205 args = append(args, getDeployImageOptions(resource)...) 206 return util.RunCmd("kubebuilder create api", "kubebuilder", args...) 207 } 208 209 func getInitArgs(store store.Store) []string { 210 var args []string 211 plugins := store.Config().GetPluginChain() 212 if len(plugins) > 0 { 213 args = append(args, "--plugins", strings.Join(plugins, ",")) 214 } 215 domain := store.Config().GetDomain() 216 if domain != "" { 217 args = append(args, "--domain", domain) 218 } 219 return args 220 } 221 222 func getGVKFlags(resource resource.Resource) []string { 223 var args []string 224 225 if len(resource.Plural) > 0 { 226 args = append(args, "--plural", resource.Plural) 227 } 228 if len(resource.Group) > 0 { 229 args = append(args, "--group", resource.Group) 230 } 231 if len(resource.Version) > 0 { 232 args = append(args, "--version", resource.Version) 233 } 234 if len(resource.Kind) > 0 { 235 args = append(args, "--kind", resource.Kind) 236 } 237 return args 238 } 239 240 func getGVKFlagsFromDeployImage(resource v1alpha1.ResourceData) []string { 241 var args []string 242 if len(resource.Group) > 0 { 243 args = append(args, "--group", resource.Group) 244 } 245 if len(resource.Version) > 0 { 246 args = append(args, "--version", resource.Version) 247 } 248 if len(resource.Kind) > 0 { 249 args = append(args, "--kind", resource.Kind) 250 } 251 return args 252 } 253 254 func getDeployImageOptions(resource v1alpha1.ResourceData) []string { 255 var args []string 256 if len(resource.Options.Image) > 0 { 257 args = append(args, fmt.Sprintf("--image=%s", resource.Options.Image)) 258 } 259 if len(resource.Options.ContainerCommand) > 0 { 260 args = append(args, fmt.Sprintf("--image-container-command=%s", resource.Options.ContainerCommand)) 261 } 262 if len(resource.Options.ContainerPort) > 0 { 263 args = append(args, fmt.Sprintf("--image-container-port=%s", resource.Options.ContainerPort)) 264 } 265 if len(resource.Options.RunAsUser) > 0 { 266 args = append(args, fmt.Sprintf("--run-as-user=%s", resource.Options.RunAsUser)) 267 } 268 args = append(args, fmt.Sprintf("--plugins=\"%s\"", "deploy-image/v1-alpha")) 269 return args 270 } 271 272 func createAPI(resource resource.Resource) error { 273 var args []string 274 args = append(args, "create") 275 args = append(args, "api") 276 args = append(args, getGVKFlags(resource)...) 277 args = append(args, getAPIResourceFlags(resource)...) 278 return util.RunCmd("kubebuilder create api", "kubebuilder", args...) 279 } 280 281 func getAPIResourceFlags(resource resource.Resource) []string { 282 var args []string 283 if resource.API == nil || resource.API.IsEmpty() { 284 // create API without creating resource 285 args = append(args, "--resource=false") 286 } else { 287 args = append(args, "--resource") 288 if resource.API.Namespaced { 289 args = append(args, "--namespaced") 290 } 291 } 292 293 if resource.Controller { 294 args = append(args, "--controller") 295 } else { 296 args = append(args, "--controller=false") 297 } 298 return args 299 } 300 301 func createWebhook(resource resource.Resource) error { 302 if resource.Webhooks == nil || resource.Webhooks.IsEmpty() { 303 return nil 304 } 305 var args []string 306 args = append(args, "create") 307 args = append(args, "webhook") 308 args = append(args, getGVKFlags(resource)...) 309 args = append(args, getWebhookResourceFlags(resource)...) 310 return util.RunCmd("kubebuilder create webhook", "kubebuilder", args...) 311 } 312 313 func getWebhookResourceFlags(resource resource.Resource) []string { 314 var args []string 315 if resource.HasConversionWebhook() { 316 args = append(args, "--conversion") 317 } 318 if resource.HasValidationWebhook() { 319 args = append(args, "--programmatic-validation") 320 } 321 if resource.HasDefaultingWebhook() { 322 args = append(args, "--defaulting") 323 } 324 return args 325 } 326 327 func copyFile(src, des string) error { 328 // nolint:gosec 329 bytesRead, err := os.ReadFile(src) 330 if err != nil { 331 return fmt.Errorf("Source file path: %s does not exist. %v", src, err) 332 } 333 //Copy all the contents to the desitination file 334 // nolint:gosec 335 return os.WriteFile(des, bytesRead, 0755) 336 } 337 338 func grafanaConfigMigrate(src, des string) error { 339 grafanaConfig := fmt.Sprintf("%s/%s", src, "grafana/custom-metrics/config.yaml") 340 if _, err := os.Stat(grafanaConfig); os.IsNotExist(err) { 341 return fmt.Errorf("Grafana Config path: %s does not exist. %v", grafanaConfig, err) 342 } 343 return copyFile(grafanaConfig, fmt.Sprintf("%s/%s", des, "grafana/custom-metrics/config.yaml")) 344 } 345 346 func kubebuilderGrafanaEdit() error { 347 args := []string{"edit", "--plugins", grafanaPluginKey} 348 err := util.RunCmd("kubebuilder edit", "kubebuilder", args...) 349 if err != nil { 350 return fmt.Errorf("Failed to run edit subcommand for Grafana Plugin %v", err) 351 } 352 return nil 353 }