Skip to content

Commit

Permalink
This commit adds a scaffold marker `+kubebuilder:scaffold:manifestsku…
Browse files Browse the repository at this point in the history
…stomizesamples` (#3645)

This commit adds a scaffold marker `+kubebuilder:scaffold:manifestskustomizesamples` to
`config/samples/kustomization.yaml` so that this file can be updated with new CR files without 
being completely overwritten. Users should now be able to add multiple samples of their CRDs
to their samples kustomization.

internal/plugins/manifests: add samples kustomization scaffold

internal/plugins/{ansible,golang,helm}: update RunCreateAPI() call with the scaffolded
resource
  • Loading branch information
Eric Stroczynski authored Aug 5, 2020
1 parent ce96b75 commit f20a796
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 30 deletions.
17 changes: 17 additions & 0 deletions changelog/fragments/config-samples-kustomize-scaffold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
entries:
- description: >
Added a scaffold marker `+kubebuilder:scaffold:manifestskustomizesamples` to `config/samples/kustomization.yaml`
that allows updates without overwriting the entire file.
kind: change
breaking: true
migration:
header: Add the samples scaffold marker to your `config/samples/kustomization.yaml`
body: >
Add the `+kubebuilder:scaffold:manifestskustomizesamples` to your `config/samples/kustomization.yaml`
file like so (using an example sample file):
```yaml
resources:
- cache_v1alpha1_memcached.yaml
# +kubebuilder:scaffold:manifestskustomizesamples
```
3 changes: 2 additions & 1 deletion internal/plugins/ansible/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ func (p *createAPIPlugin) Run() error {

// SDK phase 2 plugins.
func (p *createAPIPlugin) runPhase2() error {
return manifests.RunCreateAPI(p.config)
gvk := p.createOptions.GVK
return manifests.RunCreateAPI(p.config, config.GVK{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind})
}

func (p *createAPIPlugin) Validate() error {
Expand Down
21 changes: 18 additions & 3 deletions internal/plugins/golang/v2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func (p *createAPIPlugin) InjectConfig(c *config.Config) {
}

func (p *createAPIPlugin) Run() error {
// Run() may add a new resource to the config, so we can compare resources before/after to get the new resource.
oldResources := make(map[config.GVK]struct{}, len(p.config.Resources))
for _, r := range p.config.Resources {
oldResources[r] = struct{}{}
}
if err := p.CreateAPI.Run(); err != nil {
return err
}
Expand All @@ -49,11 +54,21 @@ func (p *createAPIPlugin) Run() error {
return nil
}

// Find the new resource. Here we shouldn't worry about checking if one was found, since downstream
// plugins will do so.
var newResource config.GVK
for _, r := range p.config.Resources {
if _, hasResource := oldResources[r]; !hasResource {
newResource = r
break
}
}

// Run SDK phase 2 plugins.
return p.runPhase2()
return p.runPhase2(newResource)
}

// SDK phase 2 plugins.
func (p *createAPIPlugin) runPhase2() error {
return manifests.RunCreateAPI(p.config)
func (p *createAPIPlugin) runPhase2(gvk config.GVK) error {
return manifests.RunCreateAPI(p.config, gvk)
}
3 changes: 2 additions & 1 deletion internal/plugins/helm/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ func (p *createAPIPlugin) Run() error {

// SDK phase 2 plugins.
func (p *createAPIPlugin) runPhase2() error {
return manifests.RunCreateAPI(p.config)
gvk := p.createOptions.GVK
return manifests.RunCreateAPI(p.config, config.GVK{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind})
}

// Validate perform the required validations for this plugin
Expand Down
62 changes: 37 additions & 25 deletions internal/plugins/manifests/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,53 @@ package manifests

import (
"fmt"
"path/filepath"
"strings"

"sigs.k8s.io/kubebuilder/pkg/model"
"sigs.k8s.io/kubebuilder/pkg/model/config"
"sigs.k8s.io/kubebuilder/pkg/model/file"

"github.com/operator-framework/operator-sdk/internal/plugins/util/kustomize"
"github.com/operator-framework/operator-sdk/internal/kubebuilder/machinery"
)

// sampleKustomizationFragment is a template for samples/kustomization.yaml.
const sampleKustomizationFragment = `## This file is auto-generated, do not modify ##
resources:
`
// RunCreateAPI runs the manifests SDK phase 2 plugin.
func RunCreateAPI(cfg *config.Config, gvk config.GVK) error {

// RunCreateAPI perform the SDK plugin-specific scaffolds.
func RunCreateAPI(cfg *config.Config) error {

// Write CR paths to the samples' kustomization file. This file has a
// "do not modify" comment so it is safe to overwrite.
samplesKustomization := sampleKustomizationFragment
for _, gvk := range cfg.Resources {
samplesKustomization += fmt.Sprintf("- %s\n", makeCRFileName(gvk))
}
kpath := filepath.Join("config", "samples")
if err := kustomize.Write(kpath, samplesKustomization); err != nil {
if err := newAPIScaffolder(cfg, gvk).scaffold(); err != nil {
return err
}

return nil
}

// todo(camilamacedo86): Now that we have the Kubebuilder scaffolding machinery included in our repo, we could make
// this an actual template that supports both file.Template and file.Inserter for init and create api, respectively.
// More info: https://github.com/operator-framework/operator-sdk/issues/3370
// makeCRFileName returns a Custom Resource example file name in the same format
// as kubebuilder's CreateAPI plugin for a gvk.
func makeCRFileName(gvk config.GVK) string {
return fmt.Sprintf("%s_%s_%s.yaml", gvk.Group, gvk.Version, strings.ToLower(gvk.Kind))
type apiScaffolder struct {
config *config.Config
gvk config.GVK
}

func newAPIScaffolder(config *config.Config, gvk config.GVK) *apiScaffolder {
return &apiScaffolder{
config: config,
gvk: gvk,
}
}

func (s *apiScaffolder) newUniverse() *model.Universe {
return model.NewUniverse(
model.WithConfig(s.config),
)
}

func (s *apiScaffolder) scaffold() error {
var builders []file.Builder
// If the gvk is non-empty, add relevant builders.
if s.gvk.Group != "" || s.gvk.Version != "" || s.gvk.Kind != "" {
builders = append(builders, &kustomization{GroupVersionKind: s.gvk})
}

err := machinery.NewScaffold().Execute(s.newUniverse(), builders...)
if err != nil {
return fmt.Errorf("error scaffolding manifests: %v", err)
}

return nil
}
75 changes: 75 additions & 0 deletions internal/plugins/manifests/samples.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2020 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 manifests

import (
"fmt"
"path/filepath"
"strings"

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

var _ file.Template = &kustomization{}
var _ file.Inserter = &kustomization{}

// kustomization scaffolds or updates the kustomization.yaml in config/samples.
type kustomization struct {
file.TemplateMixin

// GroupVersionKind is the sample's gvk to add to this scaffold.
GroupVersionKind config.GVK
}

// SetTemplateDefaults implements file.Template
func (f *kustomization) SetTemplateDefaults() error {
if f.Path == "" {
f.Path = filepath.Join("config", "samples", "kustomization.yaml")
}

f.TemplateBody = fmt.Sprintf(kustomizationTemplate, file.NewMarkerFor(f.Path, samplesMarker))

return nil
}

const (
samplesMarker = "manifestskustomizesamples"
)

// GetMarkers implements file.Inserter
func (f *kustomization) GetMarkers() []file.Marker {
return []file.Marker{file.NewMarkerFor(f.Path, samplesMarker)}
}

const samplesCodeFragment = `- %s
`

// makeCRFileName returns a Custom Resource example file name in the same format
// as kubebuilder's CreateAPI plugin for a gvk.
func makeCRFileName(gvk config.GVK) string {
return fmt.Sprintf("%s_%s_%s.yaml", gvk.Group, gvk.Version, strings.ToLower(gvk.Kind))
}

// GetCodeFragments implements file.Inserter
func (f *kustomization) GetCodeFragments() file.CodeFragmentsMap {
return file.CodeFragmentsMap{
file.NewMarkerFor(f.Path, samplesMarker): []string{fmt.Sprintf(samplesCodeFragment, makeCRFileName(f.GroupVersionKind))},
}
}

const kustomizationTemplate = `## Append samples you want in your CSV to this file as resources ##
resources:
%s
`

0 comments on commit f20a796

Please sign in to comment.