diff --git a/build/test.sh b/build/test.sh index df320ff8b4..7c22824829 100755 --- a/build/test.sh +++ b/build/test.sh @@ -33,6 +33,7 @@ export TEST_ASSET_ETCD=/tmp/kubebuilder/bin/etcd kubebuilder init repo --domain sample.kubernetes.io kubebuilder create resource --group insect --version v1beta1 --kind Bee kubebuilder create resource --group insect --version v1beta1 --kind Wasp +kubebuilder create controller --group apps --version v1beta2 --kind Deployment --core-type # Verify the controller-manager builds and the tests pass go build ./cmd/... diff --git a/cmd/internal/codegen/parse/parser.go b/cmd/internal/codegen/parse/parser.go index 1a92fc0d9a..72a8edf118 100644 --- a/cmd/internal/codegen/parse/parser.go +++ b/cmd/internal/codegen/parse/parser.go @@ -17,7 +17,13 @@ limitations under the License. package parse import ( + "go/build" "path/filepath" + "strings" + "os" + "bufio" + + "github.com/golang/glog" "github.com/kubernetes-sigs/kubebuilder/cmd/internal/codegen" "github.com/pkg/errors" @@ -114,6 +120,39 @@ func (b *APIs) parseDomain() { comments := Comments(pkg.Comments) b.Domain = comments.getTag("domain", "=") if len(b.Domain) == 0 { - panic("Could not find string matching // +domain=.+ in apis/doc.go") + b.Domain = parseDomainFromFiles(b.context.Inputs) + if len(b.Domain) == 0 { + panic("Could not find string matching // +domain=.+ in apis/doc.go") + } + } +} + +func parseDomainFromFiles(paths []string) string { + var domain string + for _, path := range paths { + if strings.HasSuffix(path, "pkg/apis") { + filePath := strings.Join([]string{build.Default.GOPATH, "src", path, "doc.go"}, "/") + lines := []string{} + + file, err := os.Open(filePath) + if err != nil { + glog.Fatal(err) + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + if strings.HasPrefix(scanner.Text(), "//") { + lines = append(lines, strings.Replace(scanner.Text(), "// ", "", 1)) + } + } + if err := scanner.Err(); err != nil { + glog.Fatal(err) + } + + comments := Comments(lines) + domain = comments.getTag("domain", "=") + break + } } + return domain } diff --git a/cmd/kubebuilder-gen/internal/controllergen/inject.go b/cmd/kubebuilder-gen/internal/controllergen/inject.go index b390d10fcf..c26934aa56 100644 --- a/cmd/kubebuilder-gen/internal/controllergen/inject.go +++ b/cmd/kubebuilder-gen/internal/controllergen/inject.go @@ -43,17 +43,21 @@ func (d *injectGenerator) Imports(c *generator.Context) []string { repo := d.Controllers[0].Repo im := []string{ - "time", "github.com/kubernetes-sigs/kubebuilder/pkg/controller", "k8s.io/client-go/rest", repo + "/pkg/controller/sharedinformers", repo + "/pkg/client/informers/externalversions", repo + "/pkg/inject/args", "rbacv1 \"k8s.io/api/rbac/v1\"", - "k8s.io/client-go/kubernetes/scheme", - "rscheme " + "\"" + repo + "/pkg/client/clientset/versioned/scheme\"", } + if len(d.APIS.Groups) > 0 { + im = append(im, []string{ + "time", + "k8s.io/client-go/kubernetes/scheme", + "rscheme " + "\"" + repo + "/pkg/client/clientset/versioned/scheme\""}... + ) + } // Import package for each controller repos := map[string]string{} for _, c := range d.Controllers { @@ -100,7 +104,7 @@ func (d *injectGenerator) Finalize(context *generator.Context, w io.Writer) erro var injectAPITemplate = ` func init() { - rscheme.AddToScheme(scheme.Scheme) + {{ $length := len .APIS.Groups }}{{if ne $length 0 }}rscheme.AddToScheme(scheme.Scheme){{ end }} // Inject Informers Inject = append(Inject, func(arguments args.InjectArgs) error { diff --git a/cmd/kubebuilder/create/resource/controller.go b/cmd/kubebuilder/create/controller/controller.go similarity index 81% rename from cmd/kubebuilder/create/resource/controller.go rename to cmd/kubebuilder/create/controller/controller.go index 9451d0465e..628f2adf0c 100644 --- a/cmd/kubebuilder/create/resource/controller.go +++ b/cmd/kubebuilder/create/controller/controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource +package controller import ( "fmt" @@ -25,7 +25,20 @@ import ( "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/util" ) -func doController(dir string, args resourceTemplateArgs) bool { +type controllerTemplateArgs struct { + BoilerPlate string + Domain string + Group string + Version string + Kind string + Resource string + Repo string + PluralizedKind string + NonNamespacedKind bool + CoreType bool +} + +func doController(dir string, args controllerTemplateArgs) bool { path := filepath.Join(dir, "pkg", "controller", strings.ToLower(createutil.KindName), "controller.go") fmt.Printf("\t%s\n", filepath.Join( "pkg", "controller", strings.ToLower(createutil.KindName), "controller.go")) @@ -42,11 +55,17 @@ import ( "github.com/kubernetes-sigs/kubebuilder/pkg/controller" "github.com/kubernetes-sigs/kubebuilder/pkg/controller/types" "k8s.io/client-go/tools/record" - + {{if .CoreType}} + {{.Group}}{{.Version}}client "k8s.io/client-go/kubernetes/typed/{{.Group}}/{{.Version}}" + {{.Group}}{{.Version}}lister "k8s.io/client-go/listers/{{.Group}}/{{.Version}}" + {{.Group}}{{.Version}} "k8s.io/api/{{.Group}}/{{.Version}}" + {{.Group}}{{.Version}}informer "k8s.io/client-go/informers/{{.Group}}/{{.Version}}" + {{else}} {{.Group}}{{.Version}}client "{{.Repo}}/pkg/client/clientset/versioned/typed/{{.Group}}/{{.Version}}" {{.Group}}{{.Version}}lister "{{.Repo}}/pkg/client/listers/{{.Group}}/{{.Version}}" {{.Group}}{{.Version}} "{{.Repo}}/pkg/apis/{{.Group}}/{{.Version}}" {{.Group}}{{.Version}}informer "{{.Repo}}/pkg/client/informers/externalversions/{{.Group}}/{{.Version}}" + {{end}} "{{.Repo}}/pkg/inject/args" ) @@ -59,7 +78,7 @@ func (bc *{{.Kind}}Controller) Reconcile(k types.ReconcileKey) error { log.Printf("Implement the Reconcile function on {{lower .Kind}}.{{.Kind}}Controller to reconcile %s\n", k.Name) return nil } - +{{if .CoreType}}// +kubebuilder:informers:group={{ .Group }},version={{ .Version }},kind={{ .Kind }}{{end}} // +kubebuilder:controller:group={{ .Group }},version={{ .Version }},kind={{ .Kind}},resource={{ .Resource }} type {{.Kind}}Controller struct { // INSERT ADDITIONAL FIELDS HERE @@ -76,7 +95,8 @@ func ProvideController(arguments args.InjectArgs) (*controller.GenericController // INSERT INITIALIZATIONS FOR ADDITIONAL FIELDS HERE bc := &{{.Kind}}Controller{ {{lower .Kind}}Lister: arguments.ControllerManager.GetInformerProvider(&{{.Group}}{{.Version}}.{{.Kind}}{}).({{.Group}}{{.Version}}informer.{{.Kind}}Informer).Lister(), - {{lower .Kind}}client: arguments.Clientset.{{title .Group}}{{title .Version}}(), + {{if .CoreType}}{{lower .Kind}}client: arguments.KubernetesClientSet.{{title .Group}}{{title .Version}}(),{{else}} + {{lower .Kind}}client: arguments.Clientset.{{title .Group}}{{title .Version}}(),{{end}} {{lower .Kind}}recorder: arguments.CreateRecorder("{{.Kind}}Controller"), } diff --git a/cmd/kubebuilder/create/resource/controllertest.go b/cmd/kubebuilder/create/controller/controllertest.go similarity index 89% rename from cmd/kubebuilder/create/resource/controllertest.go rename to cmd/kubebuilder/create/controller/controllertest.go index aa13b8b1a5..8f09c65065 100644 --- a/cmd/kubebuilder/create/resource/controllertest.go +++ b/cmd/kubebuilder/create/controller/controllertest.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource +package controller import ( "fmt" @@ -25,7 +25,7 @@ import ( "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/util" ) -func doControllerTest(dir string, args resourceTemplateArgs) bool { +func doControllerTest(dir string, args controllerTemplateArgs) bool { path := filepath.Join(dir, "pkg", "controller", strings.ToLower(createutil.KindName), fmt.Sprintf("%s_suite_test.go", strings.ToLower(createutil.KindName))) @@ -52,8 +52,7 @@ import ( "github.com/kubernetes-sigs/kubebuilder/pkg/test" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - - "{{ .Repo }}/pkg/client/clientset/versioned" + {{if not .CoreType}}"{{ .Repo }}/pkg/client/clientset/versioned"{{end}} "{{ .Repo }}/pkg/inject" "{{ .Repo }}/pkg/inject/args" ) @@ -61,7 +60,9 @@ import ( var ( testenv *test.TestEnvironment config *rest.Config + {{if not .CoreType}} cs *versioned.Clientset + {{end}} ks *kubernetes.Clientset shutdown chan struct{} ctrl *controller.GenericController @@ -77,7 +78,9 @@ var _ = BeforeSuite(func() { var err error config, err = testenv.Start() Expect(err).NotTo(HaveOccurred()) + {{if not .CoreType}} cs = versioned.NewForConfigOrDie(config) + {{end}} ks = kubernetes.NewForConfigOrDie(config) shutdown = make(chan struct{}) @@ -112,9 +115,13 @@ import ( "github.com/kubernetes-sigs/kubebuilder/pkg/controller/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - + {{if .CoreType}} + . "k8s.io/api/{{.Group}}/{{.Version}}" + . "k8s.io/client-go/kubernetes/typed/{{.Group}}/{{.Version}}" + {{else}} . "{{ .Repo }}/pkg/apis/{{ .Group }}/{{ .Version }}" . "{{ .Repo }}/pkg/client/clientset/versioned/typed/{{ .Group }}/{{ .Version }}" + {{end}} ) // EDIT THIS FILE! @@ -153,7 +160,11 @@ var _ = Describe("{{ .Kind }} controller", func() { } // Create the instance + {{if .CoreType}} + client = ks.{{title .Group}}{{title .Version}}().{{ plural .Kind }}({{ if not .NonNamespacedKind }}"default"{{ end }}) + {{else}} client = cs.{{title .Group}}{{title .Version}}().{{ plural .Kind }}({{ if not .NonNamespacedKind }}"default"{{ end }}) + {{end}} _, err := client.Create(&instance) Expect(err).ShouldNot(HaveOccurred()) diff --git a/cmd/kubebuilder/create/controller/run.go b/cmd/kubebuilder/create/controller/run.go new file mode 100644 index 0000000000..07f6032605 --- /dev/null +++ b/cmd/kubebuilder/create/controller/run.go @@ -0,0 +1,113 @@ +/* +Copyright 2018 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 controller + +import ( + "fmt" + "log" + "os" + + createutil "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/util" + generatecmd "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/generate" + "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/util" + "github.com/markbates/inflect" + "github.com/spf13/cobra" + "strings" +) + +type ControllerArguments struct { + nonNamespacedKind bool + generate bool + CoreType bool +} + +func AddCreateController(cmd *cobra.Command) { + var c ControllerArguments + + createControllerCmd := &cobra.Command{ + Use: "controller", + Short: "Creates a controller for an API group, version and resource", + Long: `Creates a controller for an API group, version and resource. + +Also creates: +- controller reconcile function +- tests for the controller +`, + Example: `# Create a controller for resource "Bee" in the "insect" group with version "v1beta" +kubebuilder create controller --group insect --version v1beta1 --kind Bee + +# Create a controller for k8s core type "Deployment" in the "apps" group with version "v1beta2" +kubebuilder create controller --group apps --version v1beta2 --kind Deployment --core-type +`, + Run: c.RunCreateController, + } + createutil.RegisterResourceFlags(createControllerCmd) + createControllerCmd.Flags().BoolVar(&c.nonNamespacedKind, "non-namespaced", false, "if set, the API kind will be non namespaced") + createControllerCmd.Flags().BoolVar(&c.generate, "generate", true, "generate controller code") + createControllerCmd.Flags().BoolVar(&c.CoreType, "core-type", false, "generate controller for core type") + cmd.AddCommand(createControllerCmd) +} + +func (c *ControllerArguments) RunCreateController(cmd *cobra.Command, args []string) { + if _, err := os.Stat("pkg"); err != nil { + log.Fatalf("could not find 'pkg' directory. must run kubebuilder init before creating controller") + } + + util.GetDomain() + c.Validate() + + cr := util.GetCopyright(createutil.Copyright) + + fmt.Printf("Creating controller ...\n") + c.CreateController(cr) + if c.generate { + fmt.Printf("Generating code for new controller... " + + "Regenerate after editing controller files by running `kubebuilder generate clean; kubebuilder generate`.\n") + generatecmd.RunGenerate(cmd, args) + } + fmt.Printf("Next: Run the controller and create an instance with:\n" + + "$ GOBIN=${PWD}/bin go install ${PWD#$GOPATH/src/}/cmd/controller-manager\n" + + "$ bin/controller-manager --kubeconfig ~/.kube/config\n" + + "$ kubectl apply -f hack/sample/" + strings.ToLower(createutil.KindName) + ".yaml\n") +} + +func (c *ControllerArguments) Validate() { + createutil.ValidateResourceFlags() +} + +func (c *ControllerArguments) CreateController(boilerplate string) { + args := controllerTemplateArgs{ + boilerplate, + util.Domain, + createutil.GroupName, + createutil.VersionName, + createutil.KindName, + createutil.ResourceName, + util.Repo, + inflect.NewDefaultRuleset().Pluralize(createutil.KindName), + c.nonNamespacedKind, + c.CoreType, + } + + dir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + fmt.Printf("Edit your controller function...\n") + doController(dir, args) + doControllerTest(dir, args) +} diff --git a/cmd/kubebuilder/create/create.go b/cmd/kubebuilder/create/create.go index 56ad90b1b6..f75d40452a 100644 --- a/cmd/kubebuilder/create/create.go +++ b/cmd/kubebuilder/create/create.go @@ -18,6 +18,7 @@ package create import ( "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/config" + "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/controller" "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/example" "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/resource" "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/util" @@ -41,6 +42,7 @@ func AddCreate(cmd *cobra.Command) { resource.AddCreateResource(createCmd) config.AddCreateConfig(createCmd) example.AddCreateExample(createCmd) + controller.AddCreateController(createCmd) } func RunCreate(cmd *cobra.Command, args []string) { diff --git a/cmd/kubebuilder/create/resource/run.go b/cmd/kubebuilder/create/resource/run.go index 08965e050c..4882bee610 100644 --- a/cmd/kubebuilder/create/resource/run.go +++ b/cmd/kubebuilder/create/resource/run.go @@ -20,9 +20,9 @@ import ( "fmt" "log" "os" - "strings" + controllerct "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/controller" createutil "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/create/util" generatecmd "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/generate" "github.com/kubernetes-sigs/kubebuilder/cmd/kubebuilder/util" @@ -113,13 +113,11 @@ func createResource(boilerplate string) { doResourceTest(dir, args) if controller { - fmt.Printf("Edit your controller function...\n") - doController(dir, args) - doControllerTest(dir, args) + fmt.Printf("Creating controller ...\n") + c := controllerct.ControllerArguments{CoreType: false} + c.CreateController(boilerplate) } - //doDockerfile(filepath.Join(dir, "build"), args) - //doExample(dir, args) fmt.Printf("Edit your sample resource instance...\n") doSample(dir, args) } diff --git a/cmd/kubebuilder/initproject/init.go b/cmd/kubebuilder/initproject/init.go index dc7a0ebb2b..f488dac466 100644 --- a/cmd/kubebuilder/initproject/init.go +++ b/cmd/kubebuilder/initproject/init.go @@ -42,12 +42,14 @@ kubebuilder init repo --domain mydomain var domain string var copyright string var bazel bool +var controllerOnly bool func AddInit(cmd *cobra.Command) { cmd.AddCommand(repoCmd) repoCmd.Flags().StringVar(&domain, "domain", "", "domain for the API groups") repoCmd.Flags().StringVar(©right, "copyright", filepath.Join("hack", "boilerplate.go.txt"), "Location of copyright boilerplate file.") repoCmd.Flags().BoolVar(&bazel, "bazel", false, "if true, setup Bazel workspace artifacts") + repoCmd.Flags().BoolVar(&controllerOnly, "controller-only", false, "if true, setup controller only") } func runInitRepo(cmd *cobra.Command, args []string) { @@ -87,7 +89,7 @@ func runInitRepo(cmd *cobra.Command, args []string) { } doDockerfile() doInject(cr) - doArgs(cr) + doArgs(cr, controllerOnly) //os.MkdirAll("bin", 0700) RunVendorInstall(nil, nil) @@ -107,6 +109,7 @@ func execute(path, templateName, templateValue string, data interface{}) { type templateArgs struct { BoilerPlate string Repo string + ControllerOnly bool } func versionCmp(v1 string, v2 string) int { diff --git a/cmd/kubebuilder/initproject/inject.go b/cmd/kubebuilder/initproject/inject.go index 2dcce5c57f..c1d7bd71b1 100644 --- a/cmd/kubebuilder/initproject/inject.go +++ b/cmd/kubebuilder/initproject/inject.go @@ -67,10 +67,11 @@ func RunAll(rargs run.RunArguments, iargs args.InjectArgs) error { } ` -func doArgs(boilerplate string) bool { +func doArgs(boilerplate string, controllerOnly bool) bool { args := templateArgs{ Repo: util.Repo, BoilerPlate: boilerplate, + ControllerOnly: controllerOnly, } path := filepath.Join("pkg", "inject", "args", "args.go") fmt.Printf("\t%s\n", filepath.Join( @@ -83,31 +84,34 @@ var argsControllerTemplate = `{{.BoilerPlate}} package args import ( - "time" + {{ if not .ControllerOnly }}"time"{{ end }} "github.com/kubernetes-sigs/kubebuilder/pkg/inject/args" "k8s.io/client-go/rest" - - "{{.Repo}}/pkg/client/clientset/versioned" - "{{.Repo}}/pkg/client/informers/externalversions" + {{ if not .ControllerOnly }} + clientset "{{.Repo}}/pkg/client/clientset/versioned" + informer "{{.Repo}}/pkg/client/informers/externalversions" + {{ end }} ) // InjectArgs are the arguments need to initialize controllers type InjectArgs struct { args.InjectArgs - - Clientset *versioned.Clientset - Informers externalversions.SharedInformerFactory + {{ if not .ControllerOnly }} + Clientset *clientset.Clientset + Informers informer.SharedInformerFactory + {{ end }} } // CreateInjectArgs returns new controller args func CreateInjectArgs(config *rest.Config) InjectArgs { - cs := versioned.NewForConfigOrDie(config) + {{ if not .ControllerOnly }}cs := clientset.NewForConfigOrDie(config){{ end }} return InjectArgs{ InjectArgs: args.CreateInjectArgs(config), + {{ if not .ControllerOnly }} Clientset: cs, - Informers: externalversions.NewSharedInformerFactory(cs, 2 * time.Minute), + Informers: informer.NewSharedInformerFactory(cs, 2 * time.Minute), {{ end }} } } ` diff --git a/test.sh b/test.sh index 556d9cc9f6..9edc6fa398 100755 --- a/test.sh +++ b/test.sh @@ -361,7 +361,7 @@ EOF kubebuilder create resource --group insect --version v1beta1 --kind Wasp - kubebuilder create resource --group ant --version v1beta1 --kind Ant + kubebuilder create resource --group ant --version v1beta1 --kind Ant --controller=false kubebuilder create config --crds --output crd.yaml # Check for ordering of generated YAML @@ -502,15 +502,61 @@ function test_docs { diff docs/reference/config.yaml $kb_orig/test/docs/expected/config.yaml } +function generate_controller { + header_text "creating controller" + kubebuilder create controller --group ant --version v1beta1 --kind Ant +} + +function update_controller_test { + # Update import + sed -i 's!"k8s.io/client-go/kubernetes/typed/apps/v1beta2"!&\n "k8s.io/api/core/v1"!' ./pkg/controller/deployment/controller_test.go + + # Fill deployment instance + sed -i 's!instance.Name = "instance-1"!&\n instance.Spec.Template.Spec.Containers = []v1.Container{{Name: "name", Image: "someimage"}}\n labels := map[string]string{"foo": "bar"}\n instance.Spec.Template.ObjectMeta.Labels = labels\n instance.Spec.Selector = \&metav1.LabelSelector{MatchLabels: labels}!' ./pkg/controller/deployment/controller_test.go +} + +function generate_coretype_controller { + header_text "generating controller for coretype Deployment" + + # Run the commands + kubebuilder init repo --domain sample.kubernetes.io --controller-only + kubebuilder create controller --group apps --version v1beta2 --kind Deployment --core-type + + # Fill the required fileds of Deployment object so that the Deployment instance can be successfully created + update_controller_test +} + +function generate_resource_with_coretype_controller { + header_text "generating CRD resource as well as controller for coretype Deployment" + + # Run the commands + kubebuilder init repo --domain sample.kubernetes.io + kubebuilder create resource --group ant --version v1beta1 --kind Ant + kubebuilder create controller --group apps --version v1beta2 --kind Deployment --core-type + + # Fill the required fileds of Deployment object so that the Deployment instance can be successfully created + update_controller_test +} + prepare_staging_dir fetch_tools build_kb -prepare_testdir_under_gopath +prepare_testdir_under_gopath generate_crd_resources +generate_controller test_docs test_generated_controller test_vendor_update # re-running controller tests post vendor update test_generated_controller + +prepare_testdir_under_gopath +generate_resource_with_coretype_controller +test_generated_controller + +prepare_testdir_under_gopath +generate_coretype_controller +test_generated_controller + exit $rc