sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/plugins/golang/deploy-image/v1alpha1/api.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1alpha1 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "strings" 24 25 log "github.com/sirupsen/logrus" 26 27 "github.com/spf13/pflag" 28 "sigs.k8s.io/kubebuilder/v3/pkg/config" 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" 32 "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" 33 goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" 34 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds" 35 ) 36 37 const ( 38 // defaultCRDVersion is the default CRD API version to scaffold. 39 defaultCRDVersion = "v1" 40 ) 41 42 const deprecateMsg = "The v1beta1 API version for CRDs and Webhooks are deprecated and are no longer supported since " + 43 "the Kubernetes release 1.22. This flag no longer required to exist in future releases. Also, we would like to " + 44 "recommend you no longer use these API versions." + 45 "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22" 46 47 var _ plugin.CreateAPISubcommand = &createAPISubcommand{} 48 49 type createAPISubcommand struct { 50 config config.Config 51 52 options *goPlugin.Options 53 54 resource *resource.Resource 55 56 // image indicates the image that will be used to scaffold the deployment 57 image string 58 59 // runMake indicates whether to run make or not after scaffolding APIs 60 runMake bool 61 62 // runManifests indicates whether to run manifests or not after scaffolding APIs 63 runManifests bool 64 65 // imageCommand indicates the command that we should use to init the deployment 66 imageContainerCommand string 67 68 // imageContainerPort indicates the port that we should use in the scaffold 69 imageContainerPort string 70 71 // runAsUser indicates the user-id used for running the container 72 runAsUser string 73 } 74 75 func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { 76 //nolint: lll 77 subcmdMeta.Description = `Scaffold the code implementation to deploy and manage your Operand which is represented by the API informed and will be reconciled by its controller. This plugin will generate the code implementation to help you out. 78 79 Note: In general, it’s recommended to have one controller responsible for managing each API created for the project to properly follow the design goals set by Controller Runtime(https://github.com/kubernetes-sigs/controller-runtime). 80 81 This plugin will work as the common behaviour of the flag --force and will scaffold the API and controller always. Use core types or external APIs is not officially support by default with. 82 ` 83 //nolint: lll 84 subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1, Kind: Frigate to represent the 85 Image: example.com/frigate:v0.0.1 and its controller with a code to deploy and manage this Operand. 86 87 Note that in the following example we are also adding the optional options to let you inform the command which should be used to create the container and initialize itvia the flag --image-container-command as the Port that should be used 88 89 - By informing the command (--image-container-command="memcached,-m=64,-o,modern,-v") your deployment will be scaffold with, i.e.: 90 91 Command: []string{"memcached","-m=64","-o","modern","-v"}, 92 93 - By informing the Port (--image-container-port) will deployment will be scaffold with, i.e: 94 95 Ports: []corev1.ContainerPort{ 96 ContainerPort: Memcached.Spec.ContainerPort, 97 Name: "Memcached", 98 }, 99 100 Therefore, the default values informed will be used to scaffold specs for the API. 101 102 %[1]s create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:1.6.15-alpine --image-container-command="memcached -m=64 modern -v" --image-container-port="11211" --plugins="deploy-image/v1-alpha" --make=false --namespaced=false 103 104 # Generate the manifests 105 make manifests 106 107 # Install CRDs into the Kubernetes cluster using kubectl apply 108 make install 109 110 # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config 111 make run 112 `, cliMeta.CommandName) 113 } 114 115 func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { 116 fs.StringVar(&p.image, "image", "", "inform the Operand image. "+ 117 "The controller will be scaffolded with an example code to deploy and manage this image.") 118 119 fs.StringVar(&p.imageContainerCommand, "image-container-command", "", "[Optional] if informed, "+ 120 "will be used to scaffold the container command that should be used to init a container to run the image in "+ 121 "the controller and its spec in the API (CRD/CR). (i.e. --image-container-command=\"memcached,-m=64,modern,-o,-v\")") 122 fs.StringVar(&p.imageContainerPort, "image-container-port", "", "[Optional] if informed, "+ 123 "will be used to scaffold the container port that should be used by container image in "+ 124 "the controller and its spec in the API (CRD/CR). (i.e --image-container-port=\"11211\") ") 125 fs.StringVar(&p.runAsUser, "run-as-user", "", "User-Id for the container formed will be set to this value") 126 127 fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files") 128 fs.BoolVar(&p.runManifests, "manifests", true, "if true, run `make manifests` after generating files") 129 130 p.options = &goPlugin.Options{} 131 132 fs.StringVar(&p.options.CRDVersion, "crd-version", defaultCRDVersion, 133 "version of CustomResourceDefinition to scaffold. Options: [v1, v1beta1]") 134 135 fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") 136 137 // (not required raise an error in this case) 138 // nolint:errcheck,gosec 139 fs.MarkDeprecated("crd-version", deprecateMsg) 140 } 141 142 func (p *createAPISubcommand) InjectConfig(c config.Config) error { 143 p.config = c 144 145 return nil 146 } 147 148 func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { 149 p.resource = res 150 p.options.DoAPI = true 151 p.options.DoController = true 152 p.options.Namespaced = true 153 154 p.options.UpdateResource(p.resource, p.config) 155 156 if err := p.resource.Validate(); err != nil { 157 return err 158 } 159 160 // Check that the provided group can be added to the project 161 if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) { 162 return fmt.Errorf("multiple groups are not allowed by default, " + 163 "to enable multi-group visit https://kubebuilder.io/migration/multi-group.html") 164 } 165 166 // Check CRDVersion against all other CRDVersions in p.config for compatibility. 167 // nolint:staticcheck 168 if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) { 169 return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", 170 p.resource.API.CRDVersion) 171 } 172 173 // Check CRDVersion against all other CRDVersions in p.config for compatibility. 174 // nolint:staticcheck 175 if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) { 176 return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", 177 p.resource.API.CRDVersion) 178 } 179 180 return nil 181 } 182 183 func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error { 184 if len(p.image) == 0 { 185 return fmt.Errorf("you MUST inform the image that will be used in the reconciliation") 186 } 187 188 isGoV3 := false 189 for _, pluginKey := range p.config.GetPluginChain() { 190 if strings.Contains(pluginKey, "go.kubebuilder.io/v3") { 191 isGoV3 = true 192 } 193 } 194 195 defaultMainPath := "cmd/main.go" 196 if isGoV3 { 197 defaultMainPath = "main.go" 198 } 199 // check if main.go is present in the cmd/ directory 200 if _, err := os.Stat(defaultMainPath); os.IsNotExist(err) { 201 return fmt.Errorf("main.go file should be present in %s", defaultMainPath) 202 } 203 204 return nil 205 } 206 207 func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { 208 log.Println("updating scaffold with deploy-image/v1alpha1 plugin...") 209 210 scaffolder := scaffolds.NewDeployImageScaffolder(p.config, 211 *p.resource, 212 p.image, 213 p.imageContainerCommand, 214 p.imageContainerPort, 215 p.runAsUser) 216 scaffolder.InjectFS(fs) 217 err := scaffolder.Scaffold() 218 if err != nil { 219 return err 220 } 221 222 // Track the resources following a declarative approach 223 cfg := PluginConfig{} 224 if err := p.config.DecodePluginConfig(pluginKey, &cfg); errors.As(err, &config.UnsupportedFieldError{}) { 225 // Config doesn't support per-plugin configuration, so we can't track them 226 } else { 227 // Fail unless they key wasn't found, which just means it is the first resource tracked 228 if err != nil && !errors.As(err, &config.PluginKeyNotFoundError{}) { 229 return err 230 } 231 configDataOptions := options{ 232 Image: p.image, 233 ContainerCommand: p.imageContainerCommand, 234 ContainerPort: p.imageContainerPort, 235 RunAsUser: p.runAsUser, 236 } 237 cfg.Resources = append(cfg.Resources, ResourceData{ 238 Group: p.resource.GVK.Group, 239 Domain: p.resource.GVK.Domain, 240 Version: p.resource.GVK.Version, 241 Kind: p.resource.GVK.Kind, 242 Options: configDataOptions, 243 }, 244 ) 245 if err := p.config.EncodePluginConfig(pluginKey, cfg); err != nil { 246 return err 247 } 248 } 249 250 return nil 251 } 252 253 func (p *createAPISubcommand) PostScaffold() error { 254 err := util.RunCmd("Update dependencies", "go", "mod", "tidy") 255 if err != nil { 256 return err 257 } 258 if p.runMake && p.resource.HasAPI() { 259 err = util.RunCmd("Running make", "make", "generate") 260 if err != nil { 261 return err 262 } 263 } 264 265 if p.runManifests && p.resource.HasAPI() { 266 err = util.RunCmd("Running make", "make", "manifests") 267 if err != nil { 268 return err 269 } 270 } 271 272 fmt.Print("Next: check the implementation of your new API and controller. " + 273 "If you do changes in the API run the manifests with:\n$ make manifests\n") 274 275 return nil 276 }