diff --git a/changelog/fragments/bugfix-csv-webhooks.yaml b/changelog/fragments/bugfix-csv-webhooks.yaml new file mode 100644 index 00000000000..6d519f56bb3 --- /dev/null +++ b/changelog/fragments/bugfix-csv-webhooks.yaml @@ -0,0 +1,32 @@ +entries: + - description: > + (manifests/v2) Added a `config/manifests` kustomize patch to remove the cert-manager + volume and volumeMount from manifests destined for `generate ` + kind: bugfix + migration: + header: (manifests/v2) Add a kustomize patch to remove the cert-manager volume/volumeMount from your CSV + body: > + OLM does [not yet support cert-manager](https://olm.operatorframework.io/docs/advanced-tasks/adding-admission-and-conversion-webhooks/#certificate-authority-requirements), + so a JSON patch was added to remove this volume and mount such that + OLM can itself create and manage certs for your Operator. + + In `config/manifests/kustomization.yaml`, add the following: + + ```yaml + patchesJson6902: + - target: + group: apps + version: v1 + kind: Deployment + name: controller-manager + namespace: system + patch: |- + # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. + # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. + - op: remove + path: /spec/template/spec/containers/1/volumeMounts/0 + # Remove the "cert" volume, since OLM will create and mount a set of certs. + # Update the indices in this path if adding or removing volumes in the manager's Deployment. + - op: remove + path: /spec/template/spec/volumes/0 + ``` diff --git a/hack/generate/samples/internal/go/v2/memcached_with_webhooks.go b/hack/generate/samples/internal/go/v2/memcached_with_webhooks.go index 2c5415b09f7..95c0839892b 100644 --- a/hack/generate/samples/internal/go/v2/memcached_with_webhooks.go +++ b/hack/generate/samples/internal/go/v2/memcached_with_webhooks.go @@ -90,7 +90,8 @@ func (mh *MemcachedGoWithWebhooks) Run() { pkg.CheckError("scaffolding webhook", err) mh.implementingWebhooks() - mh.uncommentKustomizationFile() + mh.uncommentDefaultKustomization() + mh.uncommentManifestsKustomization() log.Infof("creating the bundle") err = mh.ctx.GenerateBundle() @@ -106,35 +107,28 @@ func (mh *MemcachedGoWithWebhooks) Run() { pkg.CheckError("cleaning up", os.RemoveAll(filepath.Join(mh.ctx.Dir, "bin"))) } -// uncommentKustomizationFile will uncomment the file kustomization.yaml -func (mh *MemcachedGoWithWebhooks) uncommentKustomizationFile() { - log.Infof("uncomment kustomization.yaml to enable webhook and ca injection") - err := testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- ../webhook", "#") +// uncommentDefaultKustomization will uncomment code in config/default/kustomization.yaml +func (mh *MemcachedGoWithWebhooks) uncommentDefaultKustomization() { + var err error + kustomization := filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml") + log.Info("uncommenting config/default/kustomization.yaml to enable webhooks and ca injection") + + err = testutils.UncommentCode(kustomization, "#- ../webhook", "#") pkg.CheckError("uncomment webhook", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- ../certmanager", "#") + err = testutils.UncommentCode(kustomization, "#- ../certmanager", "#") pkg.CheckError("uncomment certmanager", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- ../prometheus", "#") + err = testutils.UncommentCode(kustomization, "#- ../prometheus", "#") pkg.CheckError("uncomment prometheus", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- manager_webhook_patch.yaml", "#") + err = testutils.UncommentCode(kustomization, "#- manager_webhook_patch.yaml", "#") pkg.CheckError("uncomment manager_webhook_patch.yaml", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- webhookcainjection_patch.yaml", "#") + err = testutils.UncommentCode(kustomization, "#- webhookcainjection_patch.yaml", "#") pkg.CheckError("uncomment webhookcainjection_patch.yaml", err) - err = testutils.UncommentCode(filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), + err = testutils.UncommentCode(kustomization, `#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR # objref: # kind: Certificate @@ -164,6 +158,32 @@ func (mh *MemcachedGoWithWebhooks) uncommentKustomizationFile() { pkg.CheckError("uncommented certificate CR", err) } +// uncommentManifestsKustomization will uncomment code in config/manifests/kustomization.yaml +func (mh *MemcachedGoWithWebhooks) uncommentManifestsKustomization() { + var err error + kustomization := filepath.Join(mh.ctx.Dir, "config", "manifests", "kustomization.yaml") + log.Info("uncommenting config/manifests/kustomization.yaml to enable webhooks in OLM") + + err = testutils.UncommentCode(kustomization, + `#patchesJson6902: +#- target: +# group: apps +# version: v1 +# kind: Deployment +# name: controller-manager +# namespace: system +# patch: |- +# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/containers/1/volumeMounts/0 +# # Remove the "cert" volume, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing volumes in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/volumes/0`, "#") + pkg.CheckError("uncommented webhook volume removal patch", err) +} + // implementingWebhooks will customize the kind wekbhok file func (mh *MemcachedGoWithWebhooks) implementingWebhooks() { log.Infof("implementing webhooks") @@ -327,7 +347,7 @@ const reconcileFragment = `// Fetch the Memcached instance return ctrl.Result{}, err } // Ask to requeue after 1 minute in order to give enough time for the - // pods be created on the cluster side and the operand be able + // pods be created on the cluster side and the operand be able // to do the next update step accurately. return ctrl.Result{RequeueAfter: time.Minute }, nil } diff --git a/hack/generate/samples/internal/go/v3/memcached_with_webhooks.go b/hack/generate/samples/internal/go/v3/memcached_with_webhooks.go index cf74fbf501b..cc6f971fcfe 100644 --- a/hack/generate/samples/internal/go/v3/memcached_with_webhooks.go +++ b/hack/generate/samples/internal/go/v3/memcached_with_webhooks.go @@ -90,7 +90,8 @@ func (mh *MemcachedGoWithWebhooks) Run() { pkg.CheckError("scaffolding webhook", err) mh.implementingWebhooks() - mh.uncommentKustomizationFile() + mh.uncommentDefaultKustomization() + mh.uncommentManifestsKustomization() log.Infof("creating the bundle") err = mh.ctx.GenerateBundle() @@ -106,35 +107,28 @@ func (mh *MemcachedGoWithWebhooks) Run() { pkg.CheckError("cleaning up", os.RemoveAll(filepath.Join(mh.ctx.Dir, "bin"))) } -// uncommentKustomizationFile will uncomment the file kustomization.yaml -func (mh *MemcachedGoWithWebhooks) uncommentKustomizationFile() { - log.Infof("uncomment kustomization.yaml to enable webhook and ca injection") - err := testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- ../webhook", "#") +// uncommentDefaultKustomization will uncomment code in config/default/kustomization.yaml +func (mh *MemcachedGoWithWebhooks) uncommentDefaultKustomization() { + var err error + kustomization := filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml") + log.Info("uncommenting config/default/kustomization.yaml to enable webhooks and ca injection") + + err = testutils.UncommentCode(kustomization, "#- ../webhook", "#") pkg.CheckError("uncomment webhook", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- ../certmanager", "#") + err = testutils.UncommentCode(kustomization, "#- ../certmanager", "#") pkg.CheckError("uncomment certmanager", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- ../prometheus", "#") + err = testutils.UncommentCode(kustomization, "#- ../prometheus", "#") pkg.CheckError("uncomment prometheus", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- manager_webhook_patch.yaml", "#") + err = testutils.UncommentCode(kustomization, "#- manager_webhook_patch.yaml", "#") pkg.CheckError("uncomment manager_webhook_patch.yaml", err) - err = testutils.UncommentCode( - filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), - "#- webhookcainjection_patch.yaml", "#") + err = testutils.UncommentCode(kustomization, "#- webhookcainjection_patch.yaml", "#") pkg.CheckError("uncomment webhookcainjection_patch.yaml", err) - err = testutils.UncommentCode(filepath.Join(mh.ctx.Dir, "config", "default", "kustomization.yaml"), + err = testutils.UncommentCode(kustomization, `#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR # objref: # kind: Certificate @@ -164,6 +158,32 @@ func (mh *MemcachedGoWithWebhooks) uncommentKustomizationFile() { pkg.CheckError("uncommented certificate CR", err) } +// uncommentManifestsKustomization will uncomment code in config/manifests/kustomization.yaml +func (mh *MemcachedGoWithWebhooks) uncommentManifestsKustomization() { + var err error + kustomization := filepath.Join(mh.ctx.Dir, "config", "manifests", "kustomization.yaml") + log.Info("uncommenting config/manifests/kustomization.yaml to enable webhooks in OLM") + + err = testutils.UncommentCode(kustomization, + `#patchesJson6902: +#- target: +# group: apps +# version: v1 +# kind: Deployment +# name: controller-manager +# namespace: system +# patch: |- +# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/containers/1/volumeMounts/0 +# # Remove the "cert" volume, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing volumes in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/volumes/0`, "#") + pkg.CheckError("uncommented webhook volume removal patch", err) +} + // implementingWebhooks will customize the kind wekbhok file func (mh *MemcachedGoWithWebhooks) implementingWebhooks() { log.Infof("implementing webhooks") @@ -321,7 +341,7 @@ const reconcileFragment = `// Fetch the Memcached instance return ctrl.Result{}, err } // Ask to requeue after 1 minute in order to give enough time for the - // pods be created on the cluster side and the operand be able + // pods be created on the cluster side and the operand be able // to do the next update step accurately. return ctrl.Result{RequeueAfter: time.Minute }, nil } diff --git a/internal/cmd/operator-sdk/generate/kustomize/manifests.go b/internal/cmd/operator-sdk/generate/kustomize/manifests.go index 6be6a7883bf..98aef48d6eb 100644 --- a/internal/cmd/operator-sdk/generate/kustomize/manifests.go +++ b/internal/cmd/operator-sdk/generate/kustomize/manifests.go @@ -22,17 +22,20 @@ import ( "strings" log "github.com/sirupsen/logrus" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation" "sigs.k8s.io/kubebuilder/v3/pkg/config" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" "sigs.k8s.io/yaml" genutil "github.com/operator-framework/operator-sdk/internal/cmd/operator-sdk/generate/internal" "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion/bases" - "github.com/operator-framework/operator-sdk/internal/plugins/util/kustomize" + manifestsv2 "github.com/operator-framework/operator-sdk/internal/plugins/manifests/v2" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" "github.com/operator-framework/operator-sdk/internal/util/projutil" ) @@ -161,14 +164,6 @@ func (c *manifestsCmd) setDefaults(cfg config.Config) error { return nil } -// kustomization.yaml file contents for manifests. this should always be written to -// config/manifests/kustomization.yaml since it only references files in config. -const manifestsKustomization = `resources: -- ../default -- ../samples -- ../scorecard -` - // run generates kustomize bundle bases and a kustomization.yaml if one does not exist. func (c manifestsCmd) run(cfg config.Config) error { @@ -188,6 +183,7 @@ func (c manifestsCmd) run(cfg config.Config) error { } } + operatorType := projutil.PluginKeyToOperatorType(cfg.GetPluginChain()) relBasePath := filepath.Join("bases", c.packageName+".clusterserviceversion.yaml") basePath := filepath.Join(c.inputDir, relBasePath) gvks, err := getGVKs(cfg) @@ -196,7 +192,7 @@ func (c manifestsCmd) run(cfg config.Config) error { } base := bases.ClusterServiceVersion{ OperatorName: c.packageName, - OperatorType: projutil.PluginKeyToOperatorType(cfg.GetPluginChain()), + OperatorType: operatorType, APIsDir: c.apisDir, Interactive: requiresInteraction(basePath, c.interactiveLevel), GVKs: gvks, @@ -224,8 +220,13 @@ func (c manifestsCmd) run(cfg config.Config) error { } // Write a kustomization.yaml to outputDir if one does not exist. - if err := kustomize.WriteIfNotExist(c.outputDir, manifestsKustomization); err != nil { - return fmt.Errorf("error writing kustomization.yaml: %v", err) + kustomization := manifestsv2.Kustomization{SupportsWebhooks: operatorType == projutil.OperatorTypeGo} + kustomization.IfExistsAction = file.Skip + err = machinery.NewScaffold(machinery.Filesystem{FS: afero.NewOsFs()}, machinery.WithConfig(cfg)).Execute( + &kustomization, + ) + if err != nil { + return fmt.Errorf("error scaffolding manifests: %v", err) } if !c.quiet { diff --git a/internal/plugins/ansible/v1/init.go b/internal/plugins/ansible/v1/init.go index b03c0c533d5..7e6848acb91 100644 --- a/internal/plugins/ansible/v1/init.go +++ b/internal/plugins/ansible/v1/init.go @@ -133,7 +133,7 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // Run SDK phase 2 plugins. - if err := p.runPhase2(); err != nil { + if err := p.runPhase2(fs); err != nil { return err } @@ -149,8 +149,8 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // SDK phase 2 plugins. -func (p *initSubcommand) runPhase2() error { - if err := manifestsv2.RunInit(p.config); err != nil { +func (p *initSubcommand) runPhase2(fs machinery.Filesystem) error { + if err := manifestsv2.RunInit(p.config, fs); err != nil { return err } if err := scorecardv2.RunInit(p.config); err != nil { diff --git a/internal/plugins/golang/v2/init.go b/internal/plugins/golang/v2/init.go index e0dcd44673f..2033c92649d 100644 --- a/internal/plugins/golang/v2/init.go +++ b/internal/plugins/golang/v2/init.go @@ -47,7 +47,7 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // Run SDK phase 2 plugins. - if err := p.runPhase2(); err != nil { + if err := p.runPhase2(fs); err != nil { return err } @@ -55,11 +55,11 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // SDK phase 2 plugins. -func (p *initSubcommand) runPhase2() error { +func (p *initSubcommand) runPhase2(fs machinery.Filesystem) error { if err := envtest.RunInit(p.config); err != nil { return err } - if err := manifestsv2.RunInit(p.config); err != nil { + if err := manifestsv2.RunInit(p.config, fs); err != nil { return err } if err := scorecardv2.RunInit(p.config); err != nil { diff --git a/internal/plugins/golang/v3/init.go b/internal/plugins/golang/v3/init.go index 9bfb8f847d3..fc5ad27ab17 100644 --- a/internal/plugins/golang/v3/init.go +++ b/internal/plugins/golang/v3/init.go @@ -46,7 +46,7 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // Run SDK phase 2 plugins. - if err := p.runPhase2(); err != nil { + if err := p.runPhase2(fs); err != nil { return err } @@ -54,8 +54,8 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // SDK phase 2 plugins. -func (p *initSubcommand) runPhase2() error { - if err := manifestsv2.RunInit(p.config); err != nil { +func (p *initSubcommand) runPhase2(fs machinery.Filesystem) error { + if err := manifestsv2.RunInit(p.config, fs); err != nil { return err } if err := scorecardv2.RunInit(p.config); err != nil { diff --git a/internal/plugins/helm/v1/init.go b/internal/plugins/helm/v1/init.go index 498ce8987e9..7010e0a0a6c 100644 --- a/internal/plugins/helm/v1/init.go +++ b/internal/plugins/helm/v1/init.go @@ -161,7 +161,7 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // Run SDK phase 2 plugins. - if err := p.runPhase2(); err != nil { + if err := p.runPhase2(fs); err != nil { return err } @@ -177,8 +177,8 @@ func (p *initSubcommand) Run(fs machinery.Filesystem) error { } // SDK phase 2 plugins. -func (p *initSubcommand) runPhase2() error { - if err := manifestsv2.RunInit(p.config); err != nil { +func (p *initSubcommand) runPhase2(fs machinery.Filesystem) error { + if err := manifestsv2.RunInit(p.config, fs); err != nil { return err } if err := scorecardv2.RunInit(p.config); err != nil { diff --git a/internal/plugins/manifests/v2/init.go b/internal/plugins/manifests/v2/init.go new file mode 100644 index 00000000000..86c85b88bac --- /dev/null +++ b/internal/plugins/manifests/v2/init.go @@ -0,0 +1,59 @@ +// Copyright 2021 The Operator-SDK 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 v2 + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +// runInit runs the manifests SDK phase 2 plugin. +func runInit(cfg config.Config, fs machinery.Filesystem) error { + + if err := newInitScaffolder(cfg).scaffold(fs); err != nil { + return err + } + + return nil +} + +type initScaffolder struct { + config config.Config +} + +func newInitScaffolder(config config.Config) *initScaffolder { + return &initScaffolder{ + config: config, + } +} + +func (s *initScaffolder) scaffold(fs machinery.Filesystem) error { + + // Only Go operator types support webhooks right now. + operatorType := projutil.PluginKeyToOperatorType(s.config.GetPluginChain()) + + err := machinery.NewScaffold(fs, machinery.WithConfig(s.config)).Execute( + &Kustomization{SupportsWebhooks: operatorType == projutil.OperatorTypeGo}, + ) + if err != nil { + return fmt.Errorf("error scaffolding manifests: %v", err) + } + + return nil +} diff --git a/internal/plugins/manifests/v2/kustomization.go b/internal/plugins/manifests/v2/kustomization.go new file mode 100644 index 00000000000..df57a40b9fe --- /dev/null +++ b/internal/plugins/manifests/v2/kustomization.go @@ -0,0 +1,76 @@ +// Copyright 2021 The Operator-SDK 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 v2 + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" +) + +var _ file.Template = &Kustomization{} + +// Kustomization scaffolds a kustomization.yaml for the manifests overlay folder. +type Kustomization struct { + file.TemplateMixin + file.ProjectNameMixin + + SupportsWebhooks bool +} + +// SetTemplateDefaults implements file.Template +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(manifestsDir, "kustomization.yaml") + } + + f.TemplateBody = kustomizationTemplate + + if f.IfExistsAction == 0 && f.IfExistsAction != file.Skip { + f.IfExistsAction = file.Error + } + + return nil +} + +const kustomizationTemplate = `# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. +resources: +- bases/{{ .ProjectName }}.clusterserviceversion.yaml +- ../default +- ../samples +- ../scorecard +{{ if .SupportsWebhooks }} +# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. +# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. +# These patches remove the unnecessary "cert" volume and its manager container volumeMount. +#patchesJson6902: +#- target: +# group: apps +# version: v1 +# kind: Deployment +# name: controller-manager +# namespace: system +# patch: |- +# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/containers/1/volumeMounts/0 +# # Remove the "cert" volume, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing volumes in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/volumes/0 +{{ end -}} +` diff --git a/internal/plugins/manifests/v2/plugin.go b/internal/plugins/manifests/v2/plugin.go index d87fe2af4a0..6f6ab8470b6 100644 --- a/internal/plugins/manifests/v2/plugin.go +++ b/internal/plugins/manifests/v2/plugin.go @@ -16,6 +16,7 @@ package v2 import ( "fmt" + "path/filepath" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" @@ -33,6 +34,8 @@ const ( var ( pluginVersion = plugin.Version{Number: 2} pluginConfigKey = plugin.Key(pluginName, pluginVersion.String()) + + manifestsDir = filepath.Join("config", "manifests") ) // Config configures this plugin, and is saved in the project config file. @@ -45,11 +48,14 @@ func HasPluginConfig(cfg config.Config) bool { } // RunInit modifies the project scaffolded by kubebuilder's Init plugin. -func RunInit(cfg config.Config) error { - // Only run these if project version is v3. +func RunInit(cfg config.Config, fs machinery.Filesystem) error { + // These will only run if project version is v3. if err := manifests.RunInit(cfg); err != nil { return err } + if err := runInit(cfg, fs); err != nil { + return err + } // Update the plugin config section with this plugin's configuration. mCfg := Config{} diff --git a/testdata/ansible/memcached-operator/config/manifests/kustomization.yaml b/testdata/ansible/memcached-operator/config/manifests/kustomization.yaml index 63ca74d72a3..705a7b9817b 100644 --- a/testdata/ansible/memcached-operator/config/manifests/kustomization.yaml +++ b/testdata/ansible/memcached-operator/config/manifests/kustomization.yaml @@ -1,4 +1,7 @@ +# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. resources: +- bases/memcached-operator.clusterserviceversion.yaml - ../default - ../samples - ../scorecard diff --git a/testdata/go/v2/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml b/testdata/go/v2/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml index 2f41e115859..03338f48b65 100644 --- a/testdata/go/v2/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml +++ b/testdata/go/v2/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml @@ -137,16 +137,7 @@ spec: requests: cpu: 100m memory: 20Mi - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true terminationGracePeriodSeconds: 10 - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: webhook-server-cert permissions: - rules: - apiGroups: diff --git a/testdata/go/v2/memcached-operator/config/manifests/kustomization.yaml b/testdata/go/v2/memcached-operator/config/manifests/kustomization.yaml index 63ca74d72a3..bb4040fcf74 100644 --- a/testdata/go/v2/memcached-operator/config/manifests/kustomization.yaml +++ b/testdata/go/v2/memcached-operator/config/manifests/kustomization.yaml @@ -1,4 +1,27 @@ +# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. resources: +- bases/memcached-operator.clusterserviceversion.yaml - ../default - ../samples - ../scorecard + +# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. +# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. +# These patches remove the unnecessary "cert" volume and its manager container volumeMount. +patchesJson6902: +- target: + group: apps + version: v1 + kind: Deployment + name: controller-manager + namespace: system + patch: |- + # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. + # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. + - op: remove + path: /spec/template/spec/containers/1/volumeMounts/0 + # Remove the "cert" volume, since OLM will create and mount a set of certs. + # Update the indices in this path if adding or removing volumes in the manager's Deployment. + - op: remove + path: /spec/template/spec/volumes/0 diff --git a/testdata/go/v3/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml b/testdata/go/v3/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml index 6b7ce44a65c..4650eab4bff 100644 --- a/testdata/go/v3/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml +++ b/testdata/go/v3/memcached-operator/bundle/manifests/memcached-operator.clusterserviceversion.yaml @@ -153,19 +153,10 @@ spec: memory: 20Mi securityContext: allowPrivilegeEscalation: false - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true securityContext: runAsNonRoot: true serviceAccountName: memcached-operator-controller-manager terminationGracePeriodSeconds: 10 - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: webhook-server-cert permissions: - rules: - apiGroups: diff --git a/testdata/go/v3/memcached-operator/config/manifests/kustomization.yaml b/testdata/go/v3/memcached-operator/config/manifests/kustomization.yaml index 63ca74d72a3..bb4040fcf74 100644 --- a/testdata/go/v3/memcached-operator/config/manifests/kustomization.yaml +++ b/testdata/go/v3/memcached-operator/config/manifests/kustomization.yaml @@ -1,4 +1,27 @@ +# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. resources: +- bases/memcached-operator.clusterserviceversion.yaml - ../default - ../samples - ../scorecard + +# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. +# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. +# These patches remove the unnecessary "cert" volume and its manager container volumeMount. +patchesJson6902: +- target: + group: apps + version: v1 + kind: Deployment + name: controller-manager + namespace: system + patch: |- + # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. + # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. + - op: remove + path: /spec/template/spec/containers/1/volumeMounts/0 + # Remove the "cert" volume, since OLM will create and mount a set of certs. + # Update the indices in this path if adding or removing volumes in the manager's Deployment. + - op: remove + path: /spec/template/spec/volumes/0 diff --git a/testdata/helm/memcached-operator/config/manifests/kustomization.yaml b/testdata/helm/memcached-operator/config/manifests/kustomization.yaml index 63ca74d72a3..705a7b9817b 100644 --- a/testdata/helm/memcached-operator/config/manifests/kustomization.yaml +++ b/testdata/helm/memcached-operator/config/manifests/kustomization.yaml @@ -1,4 +1,7 @@ +# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. resources: +- bases/memcached-operator.clusterserviceversion.yaml - ../default - ../samples - ../scorecard diff --git a/website/content/en/docs/olm-integration/generation.md b/website/content/en/docs/olm-integration/generation.md index e2ba222ebbd..25ffd6a2112 100644 --- a/website/content/en/docs/olm-integration/generation.md +++ b/website/content/en/docs/olm-integration/generation.md @@ -62,6 +62,7 @@ The following resource kinds are typically included in a CSV, which are addresse - `Role`: define Operator permissions within a namespace. - `ClusterRole`: define cluster-wide Operator permissions. - `Deployment`: define how the Operator's operand is run in pods. + - `ValidatingWebhookConfiguration`, `MutatingWebhookConfiguration`: configures webhooks for your manager to handle. - `CustomResourceDefinition`: definitions of custom objects your Operator reconciles. - Custom resource examples: examples of objects adhering to the spec of a particular CRD. @@ -71,11 +72,87 @@ This is advantageous for those who would like to take full advantage of `kustomi All fields unlabeled or labeled with _marker_ [below](#csv-fields) will be overwritten by these command, so make sure you do not `kustomize build` those fields! +#### Webhooks + +A CSV allows you to [define][olm-whs] both [admission][doc-admission-whs] and [conversion][doc-conv-whs] webhooks +at [`spec.webhookdefinitions`][wh-defs]. The `generate ` commands, described below, +will automatically add webhooks to your CSV if the following holds true: +1. A webhook configuration must be associated with a `Service` by name and namespace, +whether in a [CRD][crd-wh-serviceref] or in a [`*WebhookConfiguration`][wh-serviceref] file, +1. The associated `Service` must expose one `spec.ports[*].targetPort` that matches both `containerPort` +and `protocol` of one element in the Operator `Deployment`'s `spec.template.spec.containers[*].ports`. + +By default, the manager's Deployment is configured to mount a volume containing TLS cert data +created by [cert-manager][cert-manager] into the manager's container. +OLM does [not yet support cert-manager][olm-cert-support], so a JSON patch was added +to remove this volume and mount such that OLM can itself create and manage certs for your Operator. + +**Note (for Go Operators only):** If targeting OLM < v0.17.0, the manager's default webhook server +is not configured with the correct cert/key paths; the correct path is +`/apiserver.local.config/certificates/apiserver.{cert,key}`. +To cover this case, make the following changes to your `main.go`: + +```go +import ( + ... + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +func main() { + ... + + // Standard Manager setup. + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Host: , // May not be configured explicitly. + Port: , // May not be configured explicitly. + }) + + ... + + // Before any webhooks are registered: + var certDir, certName, keyName string + if info, err := os.Stat("/apiserver.local.config/certificates"); err == nil && info.IsDir() { + certDir = "/apiserver.local.config/certificates" + certName = "apiserver.crt" + keyName = "apiserver.key" + } + if err = mgr.Add(webhook.Server{ + Host: , // Set this only if set in ctrl.Options above. + Port: , // Set this only if set in ctrl.Options above. + CertDir: certDir, // Defaults to the correct path if unset. + CertName: certName, // Defaults to the correct path if unset. + KeyName: keyName, // Defaults to the correct path if unset. + }); err != nil { + setupLog.Error(err, "unable to add webhook server") + os.Exit(1) + } + + // Now you can register webhooks. + ... +} +``` + +**Note:** The `Service` itself will still be placed into the `manifests/` directory, +in case other Operator resources require routing. Feel free to remove it otherwise. + + +[olm-whs]:https://olm.operatorframework.io/docs/advanced-tasks/adding-admission-and-conversion-webhooks +[doc-admission-whs]:https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/ +[doc-conv-whs]:https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion +[wh-defs]:https://pkg.go.dev/github.com/operator-framework/api/pkg/operators/v1alpha1#WebhookDefinition +[crd-wh-serviceref]:https://pkg.go.dev/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1?utm_source=godoc#ServiceReference +[wh-serviceref]:https://pkg.go.dev/k8s.io/api/admissionregistration/v1?utm_source=godoc#ServiceReference +[olm-cert-support]:https://olm.operatorframework.io/docs/advanced-tasks/adding-admission-and-conversion-webhooks/#certificate-authority-requirements + + + ## Generate your first release You've recently run `operator-sdk init` and created your APIs with `operator-sdk create api`. Now you'd like to package your Operator for deployment by OLM. Your Operator is at version `v0.0.1`; the `Makefile` variable `VERSION` -should be set to `0.0.1`. You've also built your operator image, `quay.io//memcached-operator:v0.0.1`. +should be set to `0.0.1`. You've also built your operator image, `example.com/memcached-operator:v0.0.1`; +if this image tag does not match yours, swap in the correct one in the docs below. ### Bundle format @@ -220,7 +297,7 @@ packagemanifests: kustomize By default `make packagemanifests` will generate a CSV, a package manifest file, and copy CRDs in the package manifests format: ```console -$ make packagemanifests IMG=quay.io//memcached-operator:v0.0.1 +$ make packagemanifests IMG=example.com/memcached-operator:v0.0.1 $ tree ./packagemanifests ./packagemanifests ├── 0.0.1 @@ -237,13 +314,13 @@ and added a port to your manager Deployment in `config/manager/manager.yaml`. If using a bundle format, the current version of your CSV can be updated by running: ```console -$ make bundle IMG=quay.io//memcached-operator:v0.0.1 +$ make bundle IMG=example.com/memcached-operator:v0.0.1 ``` If using a package manifests format, run: ```console -$ make packagemanifests IMG=quay.io//memcached-operator:v0.0.1 +$ make packagemanifests IMG=example.com/memcached-operator:v0.0.1 ``` Running the command for either format will append your new CRD to `spec.customresourcedefinitions.owned`, @@ -254,7 +331,7 @@ fields like `spec.maintainers`. ## Upgrade your Operator Let's say you're upgrading your Operator to version `v0.0.2`, you've already updated the `VERSION` variable -in your `Makefile` to `0.0.2`, and built a new operator image `quay.io//memcached-operator:v0.0.2`. +in your `Makefile` to `0.0.2`, and built a new operator image `example.com/memcached-operator:v0.0.2`. You also want to add a new channel `beta`, and use it as the default channel. First, update `spec.replaces` in your [base CSV manifest](#kustomize-files) to the _current_ CSV name. @@ -269,13 +346,13 @@ spec: Next, upgrade your bundle. If using a bundle format, a new version of your CSV can be created by running: ```console -$ make bundle CHANNELS=beta DEFAULT_CHANNEL=beta IMG=quay.io//memcached-operator:v0.0.2 +$ make bundle CHANNELS=beta DEFAULT_CHANNEL=beta IMG=example.com/memcached-operator:v0.0.2 ``` If using a package manifests format, run: ```console -$ make packagemanifests FROM_VERSION=0.0.1 CHANNEL=beta IS_CHANNEL_DEFAULT=1 IMG=quay.io//memcached-operator:v0.0.2 +$ make packagemanifests FROM_VERSION=0.0.1 CHANNEL=beta IS_CHANNEL_DEFAULT=1 IMG=example.com/memcached-operator:v0.0.2 ``` Running the command for either format will persist user-defined fields, and updates `spec.version` and `metadata.name`. @@ -325,6 +402,7 @@ being managed, each with a `name` and `url`. - `spec.selector` _(user)_ : selectors by which the Operator can pair resources in a cluster. - `spec.icon` _(user)_ : a base64-encoded icon unique to the Operator, set in a `base64data` field with a `mediatype`. - `spec.maturity` _(user)_: the Operator's maturity, ex. `alpha`. +- `spec.webhookdefinitions`: any webhooks the Operator uses. **\*** `metadata.name` and `spec.version` will only be automatically updated from the base CSV when you set `--version` when running `generate `.