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  }