Skip to content

Commit

Permalink
Declarative plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Orive Oneca <[email protected]>
  • Loading branch information
Adirio committed Feb 8, 2021
1 parent d17eb4e commit 1649b29
Show file tree
Hide file tree
Showing 8 changed files with 525 additions and 5 deletions.
2 changes: 2 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"sigs.k8s.io/kubebuilder/v3/pkg/cli"
cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3"
declarativev1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/declarative/v1"
pluginv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2"
pluginv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3"
)
Expand All @@ -34,6 +35,7 @@ func main() {
cli.WithPlugins(
&pluginv2.Plugin{},
&pluginv3.Plugin{},
&declarativev1.Plugin{},
),
cli.WithDefaultPlugins(cfgv2.Version, &pluginv2.Plugin{}),
cli.WithDefaultPlugins(cfgv3.Version, &pluginv3.Plugin{}),
Expand Down
7 changes: 2 additions & 5 deletions generate_testdata.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,10 @@ scaffold_test_project() {
$kb create webhook --version v1 --kind Lakers --defaulting --programmatic-validation
fi
elif [[ $project =~ addon ]]; then
header_text 'enabling --pattern flag ...'
export KUBEBUILDER_ENABLE_PLUGINS=1
header_text 'Creating APIs ...'
$kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --pattern=addon
$kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false --pattern=addon
$kb create api --group crew --version v1 --kind Admiral --controller=true --resource=true --namespaced=false --make=false --pattern=addon
unset KUBEBUILDER_ENABLE_PLUGINS
fi

make all test
Expand All @@ -129,9 +126,9 @@ build_kb
# Project version 2 uses plugin go/v2 (default).
scaffold_test_project project-v2 --project-version=2
scaffold_test_project project-v2-multigroup --project-version=2
scaffold_test_project project-v2-addon --project-version=2
scaffold_test_project project-v2-addon --project-version=2 --plugins="go/v2,declarative"
# Project version 3 (default) uses plugin go/v3 (default).
scaffold_test_project project-v3
scaffold_test_project project-v3-multigroup
scaffold_test_project project-v3-addon
scaffold_test_project project-v3-addon --plugins="go/v3,declarative"
scaffold_test_project project-v3-config --component-config
104 changes: 104 additions & 0 deletions pkg/plugins/declarative/v1/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
Copyright 2021 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 v1

import (
"fmt"
"path/filepath"

"github.com/spf13/afero"

"sigs.k8s.io/kubebuilder/v3/pkg/config"
"sigs.k8s.io/kubebuilder/v3/pkg/model"
"sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
"sigs.k8s.io/kubebuilder/v3/pkg/plugin"
"sigs.k8s.io/kubebuilder/v3/pkg/plugins/declarative/v1/internal/templates"
"sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery"
"sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util"
)

const (
// kbDeclarativePattern is the sigs.k8s.io/kubebuilder-declarative-pattern version
kbDeclarativePattern = "v0.0.0-20200522144838-848d48e5b073"

exampleManifestVersion = "0.0.1"
)

var _ plugin.CreateAPISubcommand = &createAPISubcommand{}

type createAPISubcommand struct {
config config.Config

resource *resource.Resource
}

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

return nil
}

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

if !p.resource.HasAPI() || !p.resource.HasController() {
return plugin.ExitError{
Plugin: pluginName,
Reason: "declarative pattern is only supported when API and controller are scaffolded",
}
}

return nil
}

func (p *createAPISubcommand) Scaffold(fs afero.Fs) error {
fmt.Println("updating scaffold with declarative pattern...")

// Load the boilerplate
bp, err := afero.ReadFile(fs, filepath.Join("hack", "boilerplate.go.txt"))
if err != nil {
return fmt.Errorf("error updating scaffold: unable to load boilerplate: %w", err)
}
boilerplate := string(bp)

if err := machinery.NewScaffold(fs).Execute(
model.NewUniverse(
model.WithConfig(p.config),
model.WithBoilerplate(boilerplate),
model.WithResource(p.resource),
),
&templates.Types{},
&templates.Controller{},
&templates.Channel{ManifestVersion: exampleManifestVersion},
&templates.Manifest{ManifestVersion: exampleManifestVersion},
); err != nil {
return fmt.Errorf("error updating scaffold: %w", err)
}

return nil
}

func (p *createAPISubcommand) PostScaffold() error {
// Ensure that we are pinning sigs.k8s.io/kubebuilder-declarative-pattern version
err := util.RunCmd("Get controller runtime", "go", "get",
"sigs.k8s.io/kubebuilder-declarative-pattern@"+kbDeclarativePattern)
if err != nil {
return err
}

return nil
}
52 changes: 52 additions & 0 deletions pkg/plugins/declarative/v1/internal/templates/channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2021 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 templates

import (
"fmt"
"path/filepath"

"sigs.k8s.io/kubebuilder/v3/pkg/model/file"
)

var _ file.Template = &Channel{}

// Channel scaffolds the file for the channel
type Channel struct {
file.TemplateMixin

ManifestVersion string
}

// SetTemplateDefaults implements file.Template
func (f *Channel) SetTemplateDefaults() error {
if f.Path == "" {
f.Path = filepath.Join("channels", "stable")
}
fmt.Println(f.Path)

f.TemplateBody = channelTemplate

f.IfExistsAction = file.Skip

return nil
}

const channelTemplate = `# Versions for the stable channel
manifests:
- version: {{ .ManifestVersion }}
`
130 changes: 130 additions & 0 deletions pkg/plugins/declarative/v1/internal/templates/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
Copyright 2021 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 templates

import (
"path/filepath"

"sigs.k8s.io/kubebuilder/v3/pkg/model/file"
)

var _ file.Template = &Controller{}

// Controller scaffolds the file that defines the controller for a CRD or a builtin resource
// nolint:maligned
type Controller struct {
file.TemplateMixin
file.MultiGroupMixin
file.BoilerplateMixin
file.ResourceMixin
}

// SetTemplateDefaults implements file.Template
func (f *Controller) SetTemplateDefaults() error {
if f.Path == "" {
if f.MultiGroup {
f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller.go")
} else {
f.Path = filepath.Join("controllers", "%[kind]_controller.go")
}
}
f.Path = f.Resource.Replacer().Replace(f.Path)

f.TemplateBody = controllerTemplate

f.IfExistsAction = file.Overwrite

return nil
}

//nolint:lll
const controllerTemplate = `{{ .Boilerplate }}
package controllers
import (
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative"
{{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
)
var _ reconcile.Reconciler = &{{ .Resource.Kind }}Reconciler{}
// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object
type {{ .Resource.Kind }}Reconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
declarative.Reconciler
}
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch
// SetupWithManager sets up the controller with the Manager.
func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {
addon.Init()
labels := map[string]string{
"k8s-app": "{{ lower .Resource.Kind }}",
}
watchLabels := declarative.SourceLabel(mgr.GetScheme())
if err := r.Reconciler.Init(mgr, &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{},
declarative.WithObjectTransform(declarative.AddLabels(labels)),
declarative.WithOwner(declarative.SourceAsOwner),
declarative.WithLabels(watchLabels),
declarative.WithStatus(status.NewBasic(mgr.GetClient())),
// TODO: add an application to your manifest: declarative.WithObjectTransform(addon.TransformApplicationFromStatus),
// TODO: add an application to your manifest: declarative.WithManagedApplication(watchLabels),
declarative.WithObjectTransform(addon.ApplyPatches),
); err != nil {
return err
}
c, err := controller.New("{{ lower .Resource.Kind }}-controller", mgr, controller.Options{Reconciler: r})
if err != nil {
return err
}
// Watch for changes to {{ .Resource.Kind }}
err = c.Watch(&source.Kind{Type: &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}}, &handler.EnqueueRequestForObject{})
if err != nil {
return err
}
// Watch for changes to deployed objects
_, err = declarative.WatchAll(mgr.GetConfig(), c, r, watchLabels)
if err != nil {
return err
}
return nil
}
`
52 changes: 52 additions & 0 deletions pkg/plugins/declarative/v1/internal/templates/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2021 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 templates

import (
"fmt"
"path/filepath"

"sigs.k8s.io/kubebuilder/v3/pkg/model/file"
)

var _ file.Template = &Manifest{}

// Manifest scaffolds the file that acts as a placeholder for the manifest
type Manifest struct {
file.TemplateMixin
file.ResourceMixin

ManifestVersion string
}

// SetTemplateDefaults implements file.Template
func (f *Manifest) SetTemplateDefaults() error {
if f.Path == "" {
f.Path = filepath.Join("channels", "packages", "%[kind]", f.ManifestVersion, "manifest.yaml")
}
f.Path = f.Resource.Replacer().Replace(f.Path)
fmt.Println(f.Path)

f.TemplateBody = manifestTemplate

f.IfExistsAction = file.Skip

return nil
}

const manifestTemplate = `# Placeholder manifest - replace with the manifest for your addon
`
Loading

0 comments on commit 1649b29

Please sign in to comment.