Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/plugins: update instead of overwrite samples kustomization #3645

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using a config.GVK here since this plugin shouldn't "known" about the resource a phase 1 plugin created or how it was created (namespaced? native API?), just what's in the config file.

}

// 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
`