Skip to content

Commit

Permalink
Added pkg/plugins/golang/v4/ for creating new go/v4 plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
NikhilSharmaWe committed Mar 20, 2022
1 parent 721b301 commit d1f3058
Show file tree
Hide file tree
Showing 32 changed files with 3,095 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,3 @@ func (r *Reconciler) deleteExternalResources(cronJob *batch.CronJob) error {
// Ensure that delete implementation is idempotent and safe to invoke
// multiple times for same object.
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package external_indexed_field
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +kubebuilder:docs-gen:collapse=Imports

/*
Expand Down Expand Up @@ -74,4 +75,5 @@ type ConfigDeploymentList struct {
func init() {
SchemeBuilder.Register(&ConfigDeployment{}, &ConfigDeploymentList{})
}
// +kubebuilder:docs-gen:collapse=Remaining API Code

// +kubebuilder:docs-gen:collapse=Remaining API Code
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder" // Required for Watching
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler" // Required for Watching
"sigs.k8s.io/controller-runtime/pkg/handler" // Required for Watching
"sigs.k8s.io/controller-runtime/pkg/predicate" // Required for Watching
"sigs.k8s.io/controller-runtime/pkg/reconcile" // Required for Watching
"sigs.k8s.io/controller-runtime/pkg/source" // Required for Watching
"sigs.k8s.io/controller-runtime/pkg/source" // Required for Watching

appsv1 "tutorial.kubebuilder.io/project/api/v1"
)
Expand All @@ -49,14 +49,15 @@ const (
)

/*
*/
*/

// ConfigDeploymentReconciler reconciles a ConfigDeployment object
type ConfigDeploymentReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}

// +kubebuilder:docs-gen:collapse=Reconciler Declaration

/*
Expand Down Expand Up @@ -153,16 +154,16 @@ func (r *ConfigDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
}

/*
As explained in the CronJob tutorial, the controller will first register the Type that it manages, as well as the types of subresources that it controls.
Since we also want to watch ConfigMaps that are not controlled or managed by the controller, we will need to use the `Watches()` functionality as well.
The `Watches()` function is a controller-runtime API that takes:
- A Kind (i.e. `ConfigMap`)
- A mapping function that converts a `ConfigMap` object to a list of reconcile requests for `ConfigDeployments`.
We have separated this out into a separate function.
- A list of options for watching the `ConfigMaps`
- In our case, we only want the watch to be triggered when the ResourceVersion of the ConfigMap is changed.
*/
As explained in the CronJob tutorial, the controller will first register the Type that it manages, as well as the types of subresources that it controls.
Since we also want to watch ConfigMaps that are not controlled or managed by the controller, we will need to use the `Watches()` functionality as well.
The `Watches()` function is a controller-runtime API that takes:
- A Kind (i.e. `ConfigMap`)
- A mapping function that converts a `ConfigMap` object to a list of reconcile requests for `ConfigDeployments`.
We have separated this out into a separate function.
- A list of options for watching the `ConfigMaps`
- In our case, we only want the watch to be triggered when the ResourceVersion of the ConfigMap is changed.
*/

return ctrl.NewControllerManagedBy(mgr).
For(&appsv1.ConfigDeployment{}).
Expand Down Expand Up @@ -205,4 +206,4 @@ func (r *ConfigDeploymentReconciler) findObjectsForConfigMap(configMap client.Ob
}
}
return requests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package owned_resource
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +kubebuilder:docs-gen:collapse=Imports

/*
Expand Down Expand Up @@ -72,4 +73,5 @@ type SimpleDeploymentList struct {
func init() {
SchemeBuilder.Register(&SimpleDeployment{}, &SimpleDeploymentList{})
}
// +kubebuilder:docs-gen:collapse=Remaining API Code

// +kubebuilder:docs-gen:collapse=Remaining API Code
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,22 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

appsv1 "tutorial.kubebuilder.io/project/api/v1"
)

/*
*/
*/

// SimpleDeploymentReconciler reconciles a SimpleDeployment object
type SimpleDeploymentReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}

// +kubebuilder:docs-gen:collapse=Reconciler Declaration

/*
Expand Down Expand Up @@ -81,28 +82,28 @@ func (r *SimpleDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req
// +kubebuilder:docs-gen:collapse=Begin the Reconcile

/*
Build the deployment that we want to see exist within the cluster
*/
Build the deployment that we want to see exist within the cluster
*/

deployment := &kapps.Deployment{}

// Set the information you care about
deployment.Spec.Replicas = simpleDeployment.Spec.Replicas

/*
Set the controller reference, specifying that this Deployment is controlled by the SimpleDeployment being reconciled.
Set the controller reference, specifying that this Deployment is controlled by the SimpleDeployment being reconciled.
This will allow for the SimpleDeployment to be reconciled when changes to the Deployment are noticed.
*/
This will allow for the SimpleDeployment to be reconciled when changes to the Deployment are noticed.
*/
if err := controllerutil.SetControllerReference(simpleDeployment, deployment, r.scheme); err != nil {
return ctrl.Result{}, err
}

/*
Manage your Deployment.
Manage your Deployment.
- Create it if it doesn't exist.
- Update it if it is configured incorrectly.
- Create it if it doesn't exist.
- Update it if it is configured incorrectly.
*/
foundDeployment := &kapps.Deployment{}
err := r.Get(ctx, types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, foundDeployment)
Expand Down Expand Up @@ -134,4 +135,4 @@ func (r *SimpleDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&appsv1.SimpleDeployment{}).
Owns(&kapps.Deployment{}).
Complete(r)
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

212 changes: 212 additions & 0 deletions pkg/plugins/golang/v4/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*
Copyright 2020 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 v4

import (
"bufio"
"errors"
"fmt"
"os"

"github.com/spf13/pflag"

"sigs.k8s.io/kubebuilder/v3/pkg/config"
"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
"sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
"sigs.k8s.io/kubebuilder/v3/pkg/plugin"
"sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
"sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds"
)

const (
// defaultCRDVersion is the default CRD API version to scaffold.
defaultCRDVersion = "v1"
)

// DefaultMainPath is default file path of main.go
const DefaultMainPath = "main.go"

var _ plugin.CreateAPISubcommand = &createAPISubcommand{}

type createAPISubcommand struct {
config config.Config

options *goPlugin.Options

resource *resource.Resource

// Check if we have to scaffold resource and/or controller
resourceFlag *pflag.Flag
controllerFlag *pflag.Flag

// force indicates that the resource should be created even if it already exists
force bool

// runMake indicates whether to run make or not after scaffolding APIs
runMake bool
}

func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
subcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller.
If information about whether the resource and controller should be scaffolded
was not explicitly provided, it will prompt the user if they should be.
After the scaffold is written, the dependencies will be updated and
make generate will be run.
`
subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
%[1]s create api --group ship --version v1beta1 --kind Frigate
# Edit the API Scheme
nano api/v1beta1/frigate_types.go
# Edit the Controller
nano controllers/frigate/frigate_controller.go
# Edit the Controller Test
nano controllers/frigate/frigate_controller_test.go
# Generate the manifests
make manifests
# Install CRDs into the Kubernetes cluster using kubectl apply
make install
# Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
make run
`, cliMeta.CommandName)
}

func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {
fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")

fs.BoolVar(&p.force, "force", false,
"attempt to create resource even if it already exists")

p.options = &goPlugin.Options{}

fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")

fs.BoolVar(&p.options.DoAPI, "resource", true,
"if set, generate the resource without prompting the user")
p.resourceFlag = fs.Lookup("resource")
fs.StringVar(&p.options.CRDVersion, "crd-version", defaultCRDVersion,
"version of CustomResourceDefinition to scaffold. Options: [v1, v1beta1]")
fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced")

fs.BoolVar(&p.options.DoController, "controller", true,
"if set, generate the controller without prompting the user")
p.controllerFlag = fs.Lookup("controller")

// (not required raise an error in this case)
// nolint:errcheck,gosec
fs.MarkDeprecated("crd-version", deprecateMsg)
}

func (p *createAPISubcommand) InjectConfig(c config.Config) error {
p.config = c

return nil
}

func (p *createAPISubcommand) InjectResource(res *resource.Resource) error {
p.resource = res

// TODO: re-evaluate whether y/n input still makes sense. We should probably always
// scaffold the resource and controller.
// Ask for API and Controller if not specified
reader := bufio.NewReader(os.Stdin)
if !p.resourceFlag.Changed {
fmt.Println("Create Resource [y/n]")
p.options.DoAPI = util.YesNo(reader)
}
if !p.controllerFlag.Changed {
fmt.Println("Create Controller [y/n]")
p.options.DoController = util.YesNo(reader)
}

p.options.UpdateResource(p.resource, p.config)

if err := p.resource.Validate(); err != nil {
return err
}

// In case we want to scaffold a resource API we need to do some checks
if p.options.DoAPI {
// Check that resource doesn't have the API scaffolded or flag force was set
if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force {
return errors.New("API resource already exists")
}

// Check that the provided group can be added to the project
if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {
return fmt.Errorf("multiple groups are not allowed by default, " +
"to enable multi-group visit https://kubebuilder.io/migration/multi-group.html")
}

// Check CRDVersion against all other CRDVersions in p.config for compatibility.
if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) {
return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q",
p.resource.API.CRDVersion)
}
}

return nil
}

func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error {
// check if main.go is present in the root directory
if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) {
return fmt.Errorf("%s file should present in the root directory", DefaultMainPath)
}

return nil
}

func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {
scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)
scaffolder.InjectFS(fs)
return scaffolder.Scaffold()
}

func (p *createAPISubcommand) PostScaffold() error {

// Update the makefile to allow generate Webhooks to ensure backwards compatibility
// todo: it should be removed for go/v4
// nolint:lll,gosec
if p.resource.API.CRDVersion == "v1beta1" {
if err := applyScaffoldCustomizationsForVbeta1(); err != nil {
return err
}
}

err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
if err != nil {
return err
}
if p.runMake && p.resource.HasAPI() {
err = util.RunCmd("Running make", "make", "generate")
if err != nil {
return err
}
fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n")
}

return nil
}
Loading

0 comments on commit d1f3058

Please sign in to comment.