Skip to content

Commit

Permalink
feat: plugin support create api subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
em-r committed May 15, 2023
1 parent 6050502 commit bcaf33e
Show file tree
Hide file tree
Showing 7 changed files with 483 additions and 0 deletions.
3 changes: 3 additions & 0 deletions plugin/v1/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

const (
commandInit = "init"
commandApi = "create api"
commandFlags = "flags"
)

Expand Down Expand Up @@ -68,6 +69,8 @@ func Run() {
switch pluginRequest.Command {
case commandInit:
response = scaffolds.InitCmd(pluginRequest)
case commandApi:
response = scaffolds.ApiCmd(pluginRequest)
case commandFlags:
response = FlagsCmd(pluginRequest)
default:
Expand Down
4 changes: 4 additions & 0 deletions plugin/v1/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ func FlagsCmd(pr *external.PluginRequest) external.PluginResponse {

flagsToParse := pflag.NewFlagSet("flags", pflag.ContinueOnError)
flagsToParse.Bool("init", false, "sets the init flag to true")
flagsToParse.Bool("api", false, "sets the api flag to true")

flagsToParse.Parse(pr.Args)

initFlag, _ := flagsToParse.GetBool("init")
apiFlag, _ := flagsToParse.GetBool("api")

if initFlag {
pluginResponse.Flags = scaffolds.InitFlags
} else if apiFlag {
pluginResponse.Flags = scaffolds.ApiFlags
} else {
pluginResponse.Error = true
pluginResponse.ErrorMsgs = []string{
Expand Down
91 changes: 91 additions & 0 deletions plugin/v1/scaffolds/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Copyright 2023 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 scaffolds

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/afero"
"sigs.k8s.io/kubebuilder-declarative-pattern/plugin/v1/scaffolds/internal/templates"
"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
"sigs.k8s.io/kubebuilder/v3/pkg/plugin"
"sigs.k8s.io/kubebuilder/v3/pkg/plugin/external"
)

var ApiFlags = []external.Flag{}

var ApiMeta = plugin.SubcommandMetadata{
Description: "Scaffold a Kubernetes API with the declarative plugin",
Examples: "kubebuilder create api --plugins=go/v4,declarative/v1",
}

const (
exampleManifestVersion = "0.0.1"
)

// ApiCmd handles all the logic for the `create api` subcommand
func ApiCmd(pr *external.PluginRequest) external.PluginResponse {
pluginResponse := external.PluginResponse{
Command: "create api",
Universe: pr.Universe,
}

// TODO(@em-r): add logic for handling `create api` command
// GVK are required to scaffold templates, can be retrieved from PluginRequest.Args

return pluginResponse
}

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

fs := machinery.Filesystem{
FS: afero.NewOsFs(),
}

// Initialize the machinery.Scaffold that will write the files to disk
scaffold := machinery.NewScaffold(fs,
machinery.WithBoilerplate(string(boilerplate)),
)

//nolint:staticcheck
err = scaffold.Execute(
&templates.Types{},
&templates.Controller{},
&templates.Channel{ManifestVersion: exampleManifestVersion},
&templates.Manifest{ManifestVersion: exampleManifestVersion},
)

if err != nil {
return fmt.Errorf("error updating scaffold: %w", err)
}

// Update Dockerfile
// nolint:staticcheck
err = updateDockerfile()
if err != nil {
return err
}

return nil
}
52 changes: 52 additions & 0 deletions plugin/v1/scaffolds/internal/templates/channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2022 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/machinery"
)

var _ machinery.Template = &Channel{}

// Channel scaffolds the file for the channel
type Channel struct {
machinery.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 = machinery.SkipFile

return nil
}

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

var _ machinery.Template = &Controller{}

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

PackageName string
}

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

f.PackageName = "controller"

f.TemplateBody = controllerTemplate

f.IfExistsAction = machinery.OverwriteFile

return nil
}

//nolint:lll
const controllerTemplate = `{{ .Boilerplate }}
package {{ .PackageName }}
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 plugin/v1/scaffolds/internal/templates/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2022 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/machinery"
)

var _ machinery.Template = &Manifest{}

// Manifest scaffolds the file that acts as a placeholder for the manifest
type Manifest struct {
machinery.TemplateMixin
machinery.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 = machinery.SkipFile

return nil
}

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

0 comments on commit bcaf33e

Please sign in to comment.