From 61dcae6d19aa6bca280353a78e3ca31eef8169dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Orive?= Date: Sun, 14 Mar 2021 21:02:13 +0100 Subject: [PATCH] Export scaffolding machinery Signed-off-by: Adrian Orive --- Makefile | 2 +- pkg/machinery/errors.go | 157 +++++ pkg/machinery/errors_test.go | 56 ++ .../machinery/machinery_suite_test.go | 0 .../internal => }/machinery/scaffold.go | 237 +++++--- pkg/machinery/scaffold_test.go | 546 +++++++++++++++++ pkg/model/file/errors.go | 61 -- pkg/model/plugin.go | 24 - pkg/model/universe.go | 41 -- pkg/plugins/golang/v2/scaffolds/api.go | 39 +- pkg/plugins/golang/v2/scaffolds/init.go | 40 +- pkg/plugins/golang/v2/scaffolds/webhook.go | 28 +- pkg/plugins/golang/v3/scaffolds/api.go | 37 +- pkg/plugins/golang/v3/scaffolds/init.go | 40 +- pkg/plugins/golang/v3/scaffolds/webhook.go | 31 +- pkg/plugins/internal/filesystem/errors.go | 173 ------ .../internal/filesystem/errors_test.go | 77 --- pkg/plugins/internal/filesystem/filesystem.go | 181 ------ .../filesystem/filesystem_suite_test.go | 29 - .../internal/filesystem/filesystem_test.go | 151 ----- pkg/plugins/internal/filesystem/mock.go | 217 ------- pkg/plugins/internal/filesystem/mock_test.go | 448 -------------- pkg/plugins/internal/machinery/errors.go | 74 --- pkg/plugins/internal/machinery/errors_test.go | 65 -- .../internal/machinery/scaffold_test.go | 557 ------------------ 25 files changed, 1013 insertions(+), 2298 deletions(-) create mode 100644 pkg/machinery/errors.go create mode 100644 pkg/machinery/errors_test.go rename pkg/{plugins/internal => }/machinery/machinery_suite_test.go (100%) rename pkg/{plugins/internal => }/machinery/scaffold.go (60%) create mode 100644 pkg/machinery/scaffold_test.go delete mode 100644 pkg/model/file/errors.go delete mode 100644 pkg/plugins/internal/filesystem/errors.go delete mode 100644 pkg/plugins/internal/filesystem/errors_test.go delete mode 100644 pkg/plugins/internal/filesystem/filesystem.go delete mode 100644 pkg/plugins/internal/filesystem/filesystem_suite_test.go delete mode 100644 pkg/plugins/internal/filesystem/filesystem_test.go delete mode 100644 pkg/plugins/internal/filesystem/mock.go delete mode 100644 pkg/plugins/internal/filesystem/mock_test.go delete mode 100644 pkg/plugins/internal/machinery/errors.go delete mode 100644 pkg/plugins/internal/machinery/errors_test.go delete mode 100644 pkg/plugins/internal/machinery/scaffold_test.go diff --git a/Makefile b/Makefile index 389384f1658..5f0173b0776 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ test-unit: ## Run the unit tests .PHONY: test-coverage test-coverage: ## Run unit tests creating the output to report coverage - rm -rf *.out # Remove all coverage files if exists - go test -race -failfast -tags=integration -coverprofile=coverage-all.out -coverpkg="./pkg/cli/...,./pkg/config/...,./pkg/internal/...,./pkg/model/...,./pkg/plugin/...,./pkg/plugins/golang,./pkg/plugins/internal/..." ./pkg/... + go test -race -failfast -tags=integration -coverprofile=coverage-all.out -coverpkg="./pkg/cli/...,./pkg/config/...,./pkg/internal/...,./pkg/machinery/...,./pkg/model/...,./pkg/plugin/...,./pkg/plugins/golang" ./pkg/... .PHONY: test-integration test-integration: ## Run the integration tests diff --git a/pkg/machinery/errors.go b/pkg/machinery/errors.go new file mode 100644 index 00000000000..e053e156740 --- /dev/null +++ b/pkg/machinery/errors.go @@ -0,0 +1,157 @@ +/* +Copyright 2020 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 machinery + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" +) + +// This file contains the errors returned by the scaffolding machinery +// They are exported to be able to check which kind of error was returned + +// ValidateError is a wrapper error that will be used for errors returned by RequiresValidation.Validate +type ValidateError struct { + error +} + +// Unwrap implements Wrapper interface +func (e ValidateError) Unwrap() error { + return e.error +} + +// SetTemplateDefaultsError is a wrapper error that will be used for errors returned by Template.SetTemplateDefaults +type SetTemplateDefaultsError struct { + error +} + +// Unwrap implements Wrapper interface +func (e SetTemplateDefaultsError) Unwrap() error { + return e.error +} + +// PluginError is a wrapper error that will be used for errors returned by model.Plugin.Pipe +type PluginError struct { + error +} + +// Unwrap implements Wrapper interface +func (e PluginError) Unwrap() error { + return e.error +} + +// ExistsFileError is a wrapper error that will be used for errors when checking for a file existence +type ExistsFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e ExistsFileError) Unwrap() error { + return e.error +} + +// OpenFileError is a wrapper error that will be used for errors when opening a file +type OpenFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e OpenFileError) Unwrap() error { + return e.error +} + +// CreateDirectoryError is a wrapper error that will be used for errors when creating a directory +type CreateDirectoryError struct { + error +} + +// Unwrap implements Wrapper interface +func (e CreateDirectoryError) Unwrap() error { + return e.error +} + +// CreateFileError is a wrapper error that will be used for errors when creating a file +type CreateFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e CreateFileError) Unwrap() error { + return e.error +} + +// ReadFileError is a wrapper error that will be used for errors when reading a file +type ReadFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e ReadFileError) Unwrap() error { + return e.error +} + +// WriteFileError is a wrapper error that will be used for errors when writing a file +type WriteFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e WriteFileError) Unwrap() error { + return e.error +} + +// CloseFileError is a wrapper error that will be used for errors when closing a file +type CloseFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e CloseFileError) Unwrap() error { + return e.error +} + +// ModelAlreadyExistsError is returned if the file is expected not to exist but a previous model does +type ModelAlreadyExistsError struct { + path string +} + +// Error implements error interface +func (e ModelAlreadyExistsError) Error() string { + return fmt.Sprintf("failed to create %s: model already exists", e.path) +} + +// UnknownIfExistsActionError is returned if the if-exists-action is unknown +type UnknownIfExistsActionError struct { + path string + ifExistsAction file.IfExistsAction +} + +// Error implements error interface +func (e UnknownIfExistsActionError) Error() string { + return fmt.Sprintf("unknown behavior if file exists (%d) for %s", e.ifExistsAction, e.path) +} + +// FileAlreadyExistsError is returned if the file is expected not to exist but it does +type FileAlreadyExistsError struct { + path string +} + +// Error implements error interface +func (e FileAlreadyExistsError) Error() string { + return fmt.Sprintf("failed to create %s: file already exists", e.path) +} diff --git a/pkg/machinery/errors_test.go b/pkg/machinery/errors_test.go new file mode 100644 index 00000000000..d0ad7405c2b --- /dev/null +++ b/pkg/machinery/errors_test.go @@ -0,0 +1,56 @@ +/* +Copyright 2020 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 machinery + +import ( + "errors" + "path/filepath" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("Errors", func() { + var ( + path = filepath.Join("path", "to", "file") + testErr = errors.New("test error") + ) + + DescribeTable("should contain the wrapped error", + func(err error) { + Expect(errors.Is(err, testErr)).To(BeTrue()) + }, + Entry("for validate errors", ValidateError{testErr}), + Entry("for set template defaults errors", SetTemplateDefaultsError{testErr}), + Entry("for plugin errors", PluginError{testErr}), + Entry("for file existence errors", ExistsFileError{testErr}), + Entry("for file opening errors", OpenFileError{testErr}), + Entry("for directory creation errors", CreateDirectoryError{testErr}), + Entry("for file creation errors", CreateFileError{testErr}), + Entry("for file reading errors", ReadFileError{testErr}), + Entry("for file writing errors", WriteFileError{testErr}), + Entry("for file closing errors", CloseFileError{testErr}), + ) + + // NOTE: the following test increases coverage + It("should print a descriptive error message", func() { + Expect(ModelAlreadyExistsError{path}.Error()).To(ContainSubstring("model already exists")) + Expect(UnknownIfExistsActionError{path, -1}.Error()).To(ContainSubstring("unknown behavior if file exists")) + Expect(FileAlreadyExistsError{path}.Error()).To(ContainSubstring("file already exists")) + }) +}) diff --git a/pkg/plugins/internal/machinery/machinery_suite_test.go b/pkg/machinery/machinery_suite_test.go similarity index 100% rename from pkg/plugins/internal/machinery/machinery_suite_test.go rename to pkg/machinery/machinery_suite_test.go diff --git a/pkg/plugins/internal/machinery/scaffold.go b/pkg/machinery/scaffold.go similarity index 60% rename from pkg/plugins/internal/machinery/scaffold.go rename to pkg/machinery/scaffold.go index 9a761781b10..ba9cf93dba5 100644 --- a/pkg/plugins/internal/machinery/scaffold.go +++ b/pkg/machinery/scaffold.go @@ -20,17 +20,25 @@ import ( "bufio" "bytes" "fmt" - "io/ioutil" + "os" "path/filepath" "strings" "text/template" + "github.com/spf13/afero" "golang.org/x/tools/imports" - "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" "sigs.k8s.io/kubebuilder/v3/pkg/model/file" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/filesystem" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +const ( + createOrUpdate = os.O_WRONLY | os.O_CREATE | os.O_TRUNC + + defaultDirectoryPermission os.FileMode = 0700 + defaultFilePermission os.FileMode = 0600 ) var options = imports.Options{ @@ -41,32 +49,96 @@ var options = imports.Options{ } // Scaffold uses templates to scaffold new files -type Scaffold interface { - // Execute writes to disk the provided files - Execute(*model.Universe, ...file.Builder) error -} - -// scaffold implements Scaffold interface -type scaffold struct { +type Scaffold struct { // plugins is the list of plugins we should allow to transform our generated scaffolding plugins []model.Plugin // fs allows to mock the file system for tests - fs filesystem.FileSystem + fs afero.Fs + + // permissions for new directories and files + dirPerm os.FileMode + filePerm os.FileMode + + // fields to create the universe + config config.Config + boilerplate string + resource *resource.Resource } +// ScaffoldOption allows to provide optional arguments to the Scaffold +type ScaffoldOption func(*Scaffold) + // NewScaffold returns a new Scaffold with the provided plugins -func NewScaffold(fs machinery.Filesystem, plugins ...model.Plugin) Scaffold { - return &scaffold{ - plugins: plugins, - fs: filesystem.New(fs.FS), +func NewScaffold(fs Filesystem, options ...ScaffoldOption) *Scaffold { + s := &Scaffold{ + fs: fs.FS, + dirPerm: defaultDirectoryPermission, + filePerm: defaultFilePermission, + } + + for _, option := range options { + option(s) + } + + return s +} + +// WithPlugins sets the plugins to be used +func WithPlugins(plugins ...model.Plugin) ScaffoldOption { + return func(s *Scaffold) { + s.plugins = append(s.plugins, plugins...) + } +} + +// WithDirectoryPermissions sets the permissions for new directories +func WithDirectoryPermissions(dirPerm os.FileMode) ScaffoldOption { + return func(s *Scaffold) { + s.dirPerm = dirPerm + } +} + +// WithFilePermissions sets the permissions for new files +func WithFilePermissions(filePerm os.FileMode) ScaffoldOption { + return func(s *Scaffold) { + s.filePerm = filePerm + } +} + +// WithConfig provides the project configuration to the Scaffold +func WithConfig(cfg config.Config) ScaffoldOption { + return func(s *Scaffold) { + s.config = cfg + + if cfg != nil && cfg.GetRepository() != "" { + imports.LocalPrefix = cfg.GetRepository() + } + } +} + +// WithBoilerplate provides the boilerplate to the Scaffold +func WithBoilerplate(boilerplate string) ScaffoldOption { + return func(s *Scaffold) { + s.boilerplate = boilerplate + } +} + +// WithResource provides the resource to the Scaffold +func WithResource(resource *resource.Resource) ScaffoldOption { + return func(s *Scaffold) { + s.resource = resource } } -// Execute implements Scaffold.Execute -func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) error { - // Initialize the universe files - universe.Files = make(map[string]*file.File, len(files)) +// Execute writes to disk the provided files +func (s *Scaffold) Execute(files ...file.Builder) error { + // Initialize the universe + universe := &model.Universe{ + Config: s.config, + Boilerplate: s.boilerplate, + Resource: s.resource, + Files: make(map[string]*file.File, len(files)), + } // Set the repo as the local prefix so that it knows how to group imports if universe.Config != nil { @@ -80,7 +152,7 @@ func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) erro // Validate file builders if reqValFile, requiresValidation := f.(file.RequiresValidation); requiresValidation { if err := reqValFile.Validate(); err != nil { - return file.NewValidateError(err) + return ValidateError{err} } } @@ -102,7 +174,7 @@ func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) erro // Execute plugins for _, plugin := range s.plugins { if err := plugin.Pipe(universe); err != nil { - return model.NewPluginError(err) + return PluginError{err} } } @@ -117,51 +189,59 @@ func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) erro } // buildFileModel scaffolds a single file -func (scaffold) buildFileModel(t file.Template, models map[string]*file.File) error { +func (Scaffold) buildFileModel(t file.Template, models map[string]*file.File) error { // Set the template default values - err := t.SetTemplateDefaults() - if err != nil { - return file.NewSetTemplateDefaultsError(err) + if err := t.SetTemplateDefaults(); err != nil { + return SetTemplateDefaultsError{err} } + path := t.GetPath() + // Handle already existing models - if _, found := models[t.GetPath()]; found { + if _, found := models[path]; found { switch t.GetIfExistsAction() { case file.Skip: return nil case file.Error: - return modelAlreadyExistsError{t.GetPath()} + return ModelAlreadyExistsError{path} case file.Overwrite: default: - return unknownIfExistsActionError{t.GetPath(), t.GetIfExistsAction()} + return UnknownIfExistsActionError{path, t.GetIfExistsAction()} } } - m := &file.File{ - Path: t.GetPath(), - IfExistsAction: t.GetIfExistsAction(), - } - b, err := doTemplate(t) if err != nil { return err } - m.Contents = string(b) - models[m.Path] = m + models[path] = &file.File{ + Path: path, + Contents: string(b), + IfExistsAction: t.GetIfExistsAction(), + } return nil } // doTemplate executes the template for a file using the input func doTemplate(t file.Template) ([]byte, error) { - temp, err := newTemplate(t).Parse(t.GetBody()) - if err != nil { + // Create a new template.Template using the type of the Template as the name + temp := template.New(fmt.Sprintf("%T", t)) + + // Set the function map to be used + fm := file.DefaultFuncMap() + if templateWithFuncMap, hasCustomFuncMap := t.(file.UseCustomFuncMap); hasCustomFuncMap { + fm = templateWithFuncMap.GetFuncMap() + } + temp.Funcs(fm) + + // Set the template body + if _, err := temp.Parse(t.GetBody()); err != nil { return nil, err } out := &bytes.Buffer{} - err = temp.Execute(out, t) - if err != nil { + if err := temp.Execute(out, t); err != nil { return nil, err } b := out.Bytes() @@ -169,8 +249,8 @@ func doTemplate(t file.Template) ([]byte, error) { // TODO(adirio): move go-formatting to write step // gofmt the imports if filepath.Ext(t.GetPath()) == ".go" { - b, err = imports.Process(t.GetPath(), b, &options) - if err != nil { + var err error + if b, err = imports.Process(t.GetPath(), b, &options); err != nil { return nil, err } } @@ -178,18 +258,8 @@ func doTemplate(t file.Template) ([]byte, error) { return b, nil } -// newTemplate a new template with common functions -func newTemplate(t file.Template) *template.Template { - fm := file.DefaultFuncMap() - useFM, ok := t.(file.UseCustomFuncMap) - if ok { - fm = useFM.GetFuncMap() - } - return template.New(fmt.Sprintf("%T", t)).Funcs(fm) -} - // updateFileModel updates a single file -func (s scaffold) updateFileModel(i file.Inserter, models map[string]*file.File) error { +func (s Scaffold) updateFileModel(i file.Inserter, models map[string]*file.File) error { m, err := s.loadPreviousModel(i, models) if err != nil { return err @@ -230,13 +300,15 @@ func (s scaffold) updateFileModel(i file.Inserter, models map[string]*file.File) } // loadPreviousModel gets the previous model from the models map or the actual file -func (s scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.File) (*file.File, error) { +func (s Scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.File) (*file.File, error) { + path := i.GetPath() + // Lets see if we already have a model for this file - if m, found := models[i.GetPath()]; found { + if m, found := models[path]; found { // Check if there is already an scaffolded file - exists, err := s.fs.Exists(i.GetPath()) + exists, err := afero.Exists(s.fs, path) if err != nil { - return nil, err + return nil, ExistsFileError{err} } // If there is a model but no scaffolded file we return the model @@ -248,46 +320,44 @@ func (s scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.Fil switch m.IfExistsAction { case file.Skip: // File has preference - fromFile, err := s.loadModelFromFile(i.GetPath()) + fromFile, err := s.loadModelFromFile(path) if err != nil { return m, nil } return fromFile, nil case file.Error: // Writing will result in an error, so we can return error now - return nil, fileAlreadyExistsError{i.GetPath()} + return nil, FileAlreadyExistsError{path} case file.Overwrite: // Model has preference return m, nil default: - return nil, unknownIfExistsActionError{i.GetPath(), m.IfExistsAction} + return nil, UnknownIfExistsActionError{path, m.IfExistsAction} } } // There was no model - return s.loadModelFromFile(i.GetPath()) + return s.loadModelFromFile(path) } // loadModelFromFile gets the previous model from the actual file -func (s scaffold) loadModelFromFile(path string) (f *file.File, err error) { +func (s Scaffold) loadModelFromFile(path string) (f *file.File, err error) { reader, err := s.fs.Open(path) if err != nil { - return + return nil, OpenFileError{err} } defer func() { - closeErr := reader.Close() - if err == nil { - err = closeErr + if closeErr := reader.Close(); err == nil && closeErr != nil { + err = CloseFileError{closeErr} } }() - content, err := ioutil.ReadAll(reader) + content, err := afero.ReadAll(reader) if err != nil { - return + return nil, ReadFileError{err} } - f = &file.File{Path: path, Contents: string(content)} - return + return &file.File{Path: path, Contents: string(content)}, nil } // getValidCodeFragments obtains the code fragments from a file.Inserter @@ -360,11 +430,11 @@ func insertStrings(content string, codeFragmentsMap file.CodeFragmentsMap) ([]by return out.Bytes(), nil } -func (s scaffold) writeFile(f *file.File) error { +func (s Scaffold) writeFile(f *file.File) (err error) { // Check if the file to write already exists - exists, err := s.fs.Exists(f.Path) + exists, err := afero.Exists(s.fs, f.Path) if err != nil { - return err + return ExistsFileError{err} } if exists { switch f.IfExistsAction { @@ -375,16 +445,29 @@ func (s scaffold) writeFile(f *file.File) error { return nil case file.Error: // By returning an error, the file is not written and the process will fail - return fileAlreadyExistsError{f.Path} + return FileAlreadyExistsError{f.Path} } } - writer, err := s.fs.Create(f.Path) + // Create the directory if needed + if err := s.fs.MkdirAll(filepath.Dir(f.Path), s.dirPerm); err != nil { + return CreateDirectoryError{err} + } + + // Create or truncate the file + writer, err := s.fs.OpenFile(f.Path, createOrUpdate, s.filePerm) if err != nil { - return err + return CreateFileError{err} } + defer func() { + if closeErr := writer.Close(); err == nil && closeErr != nil { + err = CloseFileError{err} + } + }() - _, err = writer.Write([]byte(f.Contents)) + if _, err := writer.Write([]byte(f.Contents)); err != nil { + return WriteFileError{err} + } - return err + return nil } diff --git a/pkg/machinery/scaffold_test.go b/pkg/machinery/scaffold_test.go new file mode 100644 index 00000000000..1f3e70ab8e8 --- /dev/null +++ b/pkg/machinery/scaffold_test.go @@ -0,0 +1,546 @@ +/* +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 machinery + +import ( + "errors" + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + "github.com/spf13/afero" + + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +var _ = Describe("Scaffold", func() { + Describe("NewScaffold", func() { + It("should succeed for no option", func() { + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}) + Expect(s.plugins).To(BeNil()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.config).To(BeNil()) + Expect(s.boilerplate).To(Equal("")) + Expect(s.resource).To(BeNil()) + }) + + It("should succeed with plugins option", func() { + plugins := []model.Plugin{fakePlugin{}, fakePlugin{}} + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithPlugins(plugins...)) + Expect(s.plugins).To(Equal(plugins)) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.config).To(BeNil()) + Expect(s.boilerplate).To(Equal("")) + Expect(s.resource).To(BeNil()) + }) + + It("should succeed with directory permissions option", func() { + const dirPermissions os.FileMode = 0755 + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithDirectoryPermissions(dirPermissions)) + Expect(s.plugins).To(BeNil()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(dirPermissions)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.config).To(BeNil()) + Expect(s.boilerplate).To(Equal("")) + Expect(s.resource).To(BeNil()) + }) + + It("should succeed with file permissions option", func() { + const filePermissions os.FileMode = 0755 + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithFilePermissions(filePermissions)) + Expect(s.plugins).To(BeNil()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(filePermissions)) + Expect(s.config).To(BeNil()) + Expect(s.boilerplate).To(Equal("")) + Expect(s.resource).To(BeNil()) + }) + + It("should succeed with config option", func() { + cfg := cfgv3.New() + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithConfig(cfg)) + Expect(s.plugins).To(BeNil()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.config).NotTo(BeNil()) + Expect(s.config.GetVersion().Compare(cfgv3.Version)).To(Equal(0)) + Expect(s.boilerplate).To(Equal("")) + Expect(s.resource).To(BeNil()) + }) + + It("should succeed with boilerplate option", func() { + const boilerplate = "Copyright" + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithBoilerplate(boilerplate)) + Expect(s.plugins).To(BeNil()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.config).To(BeNil()) + Expect(s.boilerplate).To(Equal(boilerplate)) + Expect(s.resource).To(BeNil()) + }) + + It("should succeed with resource option", func() { + var res = &resource.Resource{GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }} + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithResource(res)) + Expect(s.plugins).To(BeNil()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.config).To(BeNil()) + Expect(s.boilerplate).To(Equal("")) + Expect(s.resource).NotTo(BeNil()) + Expect(s.resource.GVK.IsEqualTo(res.GVK)).To(BeTrue()) + }) + }) + + Describe("Scaffold.Execute", func() { + const ( + path = "filename" + pathGo = path + ".go" + pathYaml = path + ".yaml" + content = "Hello world!" + ) + + var ( + testErr = errors.New("error text") + + s *Scaffold + ) + + BeforeEach(func() { + s = &Scaffold{fs: afero.NewMemMapFs()} + }) + + DescribeTable("successes", + func(path, expected string, files ...file.Builder) { + Expect(s.Execute(files...)).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(expected)) + }, + Entry("should write the file", + path, content, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content}, + ), + Entry("should skip optional models if already have one", + path, content, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + ), + Entry("should overwrite required models if already have one", + path, content, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: file.Overwrite}, body: content}, + ), + Entry("should format a go file", + pathGo, "package file\n", + fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: "package file"}, + ), + ) + + DescribeTable("file builders related errors", + func(errType interface{}, files ...file.Builder) { + err := s.Execute(files...) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, errType)).To(BeTrue()) + }, + Entry("should fail if unable to validate a file builder", + &ValidateError{}, + fakeRequiresValidation{validateErr: testErr}, + ), + Entry("should fail if unable to set default values for a template", + &SetTemplateDefaultsError{}, + fakeTemplate{err: testErr}, + ), + Entry("should fail if an unexpected previous model is found", + &ModelAlreadyExistsError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: file.Error}}, + ), + Entry("should fail if behavior if-exists-action is not defined", + &UnknownIfExistsActionError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: -1}}, + ), + ) + + // Following errors are unwrapped, so we need to check for substrings + DescribeTable("template related errors", + func(errMsg string, files ...file.Builder) { + err := s.Execute(files...) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errMsg)) + }, + Entry("should fail if a template is broken", + "template: ", + fakeTemplate{body: "{{ .Field }"}, + ), + Entry("should fail if a template params aren't provided", + "template: ", + fakeTemplate{body: "{{ .Field }}"}, + ), + Entry("should fail if unable to format a go file", + "expected 'package', found ", + fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: content}, + ), + ) + + DescribeTable("insert strings", + func(path, input, expected string, files ...file.Builder) { + Expect(afero.WriteFile(s.fs, path, []byte(input), 0666)).To(Succeed()) + + Expect(s.Execute(files...)).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(expected)) + }, + Entry("should insert lines for go files", + pathGo, + `package test + +//+kubebuilder:scaffold:- +`, + `package test + +var a int +var b int + +//+kubebuilder:scaffold:- +`, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathGo}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathGo, "-"): {"var a int\n", "var b int\n"}, + }, + }, + ), + Entry("should insert lines for yaml files", + pathYaml, + ` +#+kubebuilder:scaffold:- +`, + ` +1 +2 +#+kubebuilder:scaffold:- +`, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + }, + }, + ), + Entry("should use models if there is no file", + pathYaml, + "", + ` +1 +2 +#+kubebuilder:scaffold:- +`, + fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: file.Overwrite}, body: ` +#+kubebuilder:scaffold:- +`}, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + }, + }, + ), + Entry("should use required models over files", + pathYaml, + content, + ` +1 +2 +#+kubebuilder:scaffold:- +`, + fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: file.Overwrite}, body: ` +#+kubebuilder:scaffold:- +`}, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + }, + }, + ), + Entry("should use files over optional models", + pathYaml, + ` +#+kubebuilder:scaffold:- +`, + ` +1 +2 +#+kubebuilder:scaffold:- +`, + fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml}, body: content}, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + }, + }, + ), + Entry("should filter invalid markers", + pathYaml, + ` +#+kubebuilder:scaffold:- +#+kubebuilder:scaffold:* +`, + ` +1 +2 +#+kubebuilder:scaffold:- +#+kubebuilder:scaffold:* +`, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + markers: []file.Marker{file.NewMarkerFor(pathYaml, "-")}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + file.NewMarkerFor(pathYaml, "*"): {"3\n", "4\n"}, + }, + }, + ), + Entry("should filter already existing one-line code fragments", + pathYaml, + ` +1 +#+kubebuilder:scaffold:- +3 +4 +#+kubebuilder:scaffold:* +`, + ` +1 +2 +#+kubebuilder:scaffold:- +3 +4 +#+kubebuilder:scaffold:* +`, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + file.NewMarkerFor(pathYaml, "*"): {"3\n", "4\n"}, + }, + }, + ), + Entry("should not insert anything if no code fragment", + pathYaml, + ` +#+kubebuilder:scaffold:- +`, + ` +#+kubebuilder:scaffold:- +`, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: file.CodeFragmentsMap{ + file.NewMarkerFor(pathYaml, "-"): {}, + }, + }, + ), + ) + + DescribeTable("insert strings related errors", + func(errType interface{}, files ...file.Builder) { + Expect(afero.WriteFile(s.fs, path, []byte{}, 0666)).To(Succeed()) + + err := s.Execute(files...) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, errType)).To(BeTrue()) + }, + Entry("should fail if inserting into a model that fails when a file exists and it does exist", + &FileAlreadyExistsError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}}, + fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, + ), + Entry("should fail if inserting into a model with unknown behavior if the file exists and it does exist", + &UnknownIfExistsActionError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, + fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, + ), + ) + + It("should fail if a plugin fails", func() { + s.plugins = []model.Plugin{fakePlugin{testErr}} + + err := s.Execute(fakeTemplate{}) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &PluginError{})).To(BeTrue()) + }) + + Context("write when the file already exists", func() { + BeforeEach(func() { + _ = afero.WriteFile(s.fs, path, []byte{}, 0666) + }) + + It("should skip the file by default", func() { + Expect(s.Execute(fakeTemplate{ + fakeBuilder: fakeBuilder{path: path}, + body: content, + })).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(BeEmpty()) + }) + + It("should write the file if configured to do so", func() { + Expect(s.Execute(fakeTemplate{ + fakeBuilder: fakeBuilder{path: path, ifExistsAction: file.Overwrite}, + body: content, + })).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(content)) + }) + + It("should error if configured to do so", func() { + err := s.Execute(fakeTemplate{ + fakeBuilder: fakeBuilder{path: path, ifExistsAction: file.Error}, + body: content, + }) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &FileAlreadyExistsError{})).To(BeTrue()) + }) + }) + }) +}) + +var _ model.Plugin = fakePlugin{} + +// fakePlugin is used to mock a model.Plugin in order to test Scaffold +type fakePlugin struct { + err error +} + +// Pipe implements model.Plugin +func (f fakePlugin) Pipe(_ *model.Universe) error { + return f.err +} + +var _ file.Builder = fakeBuilder{} + +// fakeBuilder is used to mock a file.Builder +type fakeBuilder struct { + path string + ifExistsAction file.IfExistsAction +} + +// GetPath implements file.Builder +func (f fakeBuilder) GetPath() string { + return f.path +} + +// GetIfExistsAction implements file.Builder +func (f fakeBuilder) GetIfExistsAction() file.IfExistsAction { + return f.ifExistsAction +} + +var _ file.RequiresValidation = fakeRequiresValidation{} + +// fakeRequiresValidation is used to mock a file.RequiresValidation in order to test Scaffold +type fakeRequiresValidation struct { + fakeBuilder + + validateErr error +} + +// Validate implements file.RequiresValidation +func (f fakeRequiresValidation) Validate() error { + return f.validateErr +} + +var _ file.Template = fakeTemplate{} + +// fakeTemplate is used to mock a file.File in order to test Scaffold +type fakeTemplate struct { + fakeBuilder + + body string + err error +} + +// GetBody implements file.Template +func (f fakeTemplate) GetBody() string { + return f.body +} + +// SetTemplateDefaults implements file.Template +func (f fakeTemplate) SetTemplateDefaults() error { + if f.err != nil { + return f.err + } + + return nil +} + +type fakeInserter struct { + fakeBuilder + + markers []file.Marker + codeFragments file.CodeFragmentsMap +} + +// GetMarkers implements file.UpdatableTemplate +func (f fakeInserter) GetMarkers() []file.Marker { + if f.markers != nil { + return f.markers + } + + markers := make([]file.Marker, 0, len(f.codeFragments)) + for marker := range f.codeFragments { + markers = append(markers, marker) + } + return markers +} + +// GetCodeFragments implements file.UpdatableTemplate +func (f fakeInserter) GetCodeFragments() file.CodeFragmentsMap { + return f.codeFragments +} diff --git a/pkg/model/file/errors.go b/pkg/model/file/errors.go deleted file mode 100644 index 20349f55844..00000000000 --- a/pkg/model/file/errors.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2020 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 file - -import ( - "errors" -) - -// validateError is a wrapper error that will be used for errors returned by RequiresValidation.Validate -type validateError struct { - error -} - -// NewValidateError wraps an error to specify it was returned by RequiresValidation.Validate -func NewValidateError(err error) error { - return validateError{err} -} - -// Unwrap implements Wrapper interface -func (e validateError) Unwrap() error { - return e.error -} - -// IsValidateError checks if the error was returned by RequiresValidation.Validate -func IsValidateError(err error) bool { - return errors.As(err, &validateError{}) -} - -// setTemplateDefaultsError is a wrapper error that will be used for errors returned by Template.SetTemplateDefaults -type setTemplateDefaultsError struct { - error -} - -// NewSetTemplateDefaultsError wraps an error to specify it was returned by Template.SetTemplateDefaults -func NewSetTemplateDefaultsError(err error) error { - return setTemplateDefaultsError{err} -} - -// Unwrap implements Wrapper interface -func (e setTemplateDefaultsError) Unwrap() error { - return e.error -} - -// IsSetTemplateDefaultsError checks if the error was returned by Template.SetTemplateDefaults -func IsSetTemplateDefaultsError(err error) bool { - return errors.As(err, &setTemplateDefaultsError{}) -} diff --git a/pkg/model/plugin.go b/pkg/model/plugin.go index 57d326fa378..06f8854933e 100644 --- a/pkg/model/plugin.go +++ b/pkg/model/plugin.go @@ -16,33 +16,9 @@ limitations under the License. package model -import ( - "errors" -) - // Plugin is the interface that a plugin must implement // We will (later) have an ExecPlugin that implements this by exec-ing a binary type Plugin interface { // Pipe is the core plugin interface, that transforms a UniverseModel Pipe(*Universe) error } - -// pluginError is a wrapper error that will be used for errors returned by Plugin.Pipe -type pluginError struct { - error -} - -// NewPluginError wraps an error to specify it was returned by Plugin.Pipe -func NewPluginError(err error) error { - return pluginError{err} -} - -// Unwrap implements Wrapper interface -func (e pluginError) Unwrap() error { - return e.error -} - -// IsPluginError checks if the error was returned by Plugin.Pipe -func IsPluginError(err error) bool { - return errors.As(err, &pluginError{}) -} diff --git a/pkg/model/universe.go b/pkg/model/universe.go index 15d597c88f7..79bd8dcb15b 100644 --- a/pkg/model/universe.go +++ b/pkg/model/universe.go @@ -37,47 +37,6 @@ type Universe struct { Files map[string]*file.File `json:"files,omitempty"` } -// NewUniverse creates a new Universe -func NewUniverse(options ...UniverseOption) *Universe { - universe := &Universe{} - - // Apply options - for _, option := range options { - option(universe) - } - - return universe -} - -// UniverseOption configure Universe -type UniverseOption func(*Universe) - -// WithConfig stores the already loaded project configuration -func WithConfig(projectConfig config.Config) UniverseOption { - return func(universe *Universe) { - universe.Config = projectConfig - } -} - -// WithBoilerplate stores the already loaded project configuration -func WithBoilerplate(boilerplate string) UniverseOption { - return func(universe *Universe) { - universe.Boilerplate = boilerplate - } -} - -// WithoutBoilerplate is used for files that do not require a boilerplate -func WithoutBoilerplate(universe *Universe) { - universe.Boilerplate = "" -} - -// WithResource stores the provided resource -func WithResource(resource *resource.Resource) UniverseOption { - return func(universe *Universe) { - universe.Resource = resource - } -} - // InjectInto injects fields from the universe into the builder func (u Universe) InjectInto(builder file.Builder) { // Inject project configuration diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go index 280b24114f0..01ff4a74af5 100644 --- a/pkg/plugins/golang/v2/scaffolds/api.go +++ b/pkg/plugins/golang/v2/scaffolds/api.go @@ -35,7 +35,6 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - internalmachinery "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) // KbDeclarativePattern is the sigs.k8s.io/kubebuilder-declarative-pattern version @@ -47,9 +46,8 @@ var _ cmdutil.Scaffolder = &apiScaffolder{} // apiScaffolder contains configuration for generating scaffolding for Go type // representing the API and controller that implements the behavior for the API. type apiScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource // fs is the filesystem that will be used by the scaffolder fs machinery.Filesystem @@ -81,24 +79,23 @@ func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs } -func (s *apiScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), - ) -} - // Scaffold implements cmdutil.Scaffolder func (s *apiScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") // Load the boilerplate - bp, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) if err != nil { return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err) } - s.boilerplate = string(bp) + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithPlugins(s.plugins...), + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) // Keep track of these values before the update doAPI := s.resource.HasAPI() @@ -117,9 +114,7 @@ func (s *apiScaffolder) Scaffold() error { } if doAPI { - - if err := internalmachinery.NewScaffold(s.fs, s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Types{Force: s.force}, &api.Group{}, &samples.CRDSample{Force: s.force}, @@ -131,19 +126,16 @@ func (s *apiScaffolder) Scaffold() error { return fmt.Errorf("error scaffolding APIs: %w", err) } - if err := internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(), + if err := scaffold.Execute( &crd.Kustomization{}, &crd.KustomizeConfig{}, ); err != nil { return fmt.Errorf("error scaffolding kustomization: %v", err) } - } if doController { - if err := internalmachinery.NewScaffold(s.fs, s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &controllers.SuiteTest{Force: s.force}, &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, ); err != nil { @@ -151,8 +143,7 @@ func (s *apiScaffolder) Scaffold() error { } } - if err := internalmachinery.NewScaffold(s.fs, s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &templates.MainUpdater{WireResource: doAPI, WireController: doController}, ); err != nil { return fmt.Errorf("error updating main.go: %v", err) diff --git a/pkg/plugins/golang/v2/scaffolds/init.go b/pkg/plugins/golang/v2/scaffolds/init.go index bf4b7dbff3e..1c61b4e994a 100644 --- a/pkg/plugins/golang/v2/scaffolds/init.go +++ b/pkg/plugins/golang/v2/scaffolds/init.go @@ -18,13 +18,11 @@ package scaffolds import ( "fmt" - "path/filepath" "github.com/spf13/afero" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" - "sigs.k8s.io/kubebuilder/v3/pkg/model" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault" @@ -34,7 +32,6 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - internalmachinery "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) const ( @@ -64,7 +61,7 @@ type initScaffolder struct { func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaffolder { return &initScaffolder{ config: config, - boilerplatePath: filepath.Join("hack", "boilerplate.go.txt"), + boilerplatePath: hack.DefaultBoilerplatePath, license: license, owner: owner, } @@ -75,25 +72,23 @@ func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs } -func (s *initScaffolder) newUniverse(boilerplate string) *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(boilerplate), - ) -} - // Scaffold implements cmdutil.Scaffolder func (s *initScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - bpFile := &hack.Boilerplate{} + // Initialize the machinery.Scaffold that will write the boilerplate file to disk + // The boilerplate file needs to be scaffolded as a separate step as it is going to + // be used by the rest of the files, even those scaffolded in this command call. + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + bpFile := &hack.Boilerplate{ + License: s.license, + Owner: s.owner, + } bpFile.Path = s.boilerplatePath - bpFile.License = s.license - bpFile.Owner = s.owner - if err := internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(""), - bpFile, - ); err != nil { + if err := scaffold.Execute(bpFile); err != nil { return err } @@ -102,8 +97,13 @@ func (s *initScaffolder) Scaffold() error { return err } - return internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(string(boilerplate)), + // Initialize the machinery.Scaffold that will write the files to disk + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + ) + + return scaffold.Execute( &templates.GitIgnore{}, &rbac.AuthProxyRole{}, &rbac.AuthProxyRoleBinding{}, diff --git a/pkg/plugins/golang/v2/scaffolds/webhook.go b/pkg/plugins/golang/v2/scaffolds/webhook.go index b9475320ef3..72c58907d16 100644 --- a/pkg/plugins/golang/v2/scaffolds/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/webhook.go @@ -23,21 +23,18 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" - "sigs.k8s.io/kubebuilder/v3/pkg/model" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - internalmachinery "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) var _ cmdutil.Scaffolder = &webhookScaffolder{} type webhookScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource // fs is the filesystem that will be used by the scaffolder fs machinery.Filesystem @@ -56,31 +53,28 @@ func (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs } -func (s *webhookScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), - ) -} - // Scaffold implements cmdutil.Scaffolder func (s *webhookScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") // Load the boilerplate - bp, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) if err != nil { return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err) } - s.boilerplate = string(bp) + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) if err := s.config.UpdateResource(s.resource); err != nil { return fmt.Errorf("error updating resource: %w", err) } - if err := internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Webhook{}, &templates.MainUpdater{WireWebhook: true}, ); err != nil { diff --git a/pkg/plugins/golang/v3/scaffolds/api.go b/pkg/plugins/golang/v3/scaffolds/api.go index e8aafbd325e..da60222bad7 100644 --- a/pkg/plugins/golang/v3/scaffolds/api.go +++ b/pkg/plugins/golang/v3/scaffolds/api.go @@ -34,7 +34,6 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - internalmachinery "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) var _ cmdutil.Scaffolder = &apiScaffolder{} @@ -42,9 +41,8 @@ var _ cmdutil.Scaffolder = &apiScaffolder{} // apiScaffolder contains configuration for generating scaffolding for Go type // representing the API and controller that implements the behavior for the API. type apiScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource // fs is the filesystem that will be used by the scaffolder fs machinery.Filesystem @@ -76,24 +74,23 @@ func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs } -func (s *apiScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), - ) -} - // Scaffold implements cmdutil.Scaffolder func (s *apiScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") // Load the boilerplate - bp, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) if err != nil { return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err) } - s.boilerplate = string(bp) + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithPlugins(s.plugins...), + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) // Keep track of these values before the update doAPI := s.resource.HasAPI() @@ -105,8 +102,7 @@ func (s *apiScaffolder) Scaffold() error { if doAPI { - if err := internalmachinery.NewScaffold(s.fs, s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Types{Force: s.force}, &api.Group{}, &samples.CRDSample{Force: s.force}, @@ -118,8 +114,7 @@ func (s *apiScaffolder) Scaffold() error { return fmt.Errorf("error scaffolding APIs: %v", err) } - if err := internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(), + if err := scaffold.Execute( &crd.Kustomization{}, &crd.KustomizeConfig{}, ); err != nil { @@ -129,8 +124,7 @@ func (s *apiScaffolder) Scaffold() error { } if doController { - if err := internalmachinery.NewScaffold(s.fs, s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &controllers.SuiteTest{Force: s.force}, &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, ); err != nil { @@ -138,8 +132,7 @@ func (s *apiScaffolder) Scaffold() error { } } - if err := internalmachinery.NewScaffold(s.fs, s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &templates.MainUpdater{WireResource: doAPI, WireController: doController}, ); err != nil { return fmt.Errorf("error updating main.go: %v", err) diff --git a/pkg/plugins/golang/v3/scaffolds/init.go b/pkg/plugins/golang/v3/scaffolds/init.go index 4aab98b554f..e86b4e7379c 100644 --- a/pkg/plugins/golang/v3/scaffolds/init.go +++ b/pkg/plugins/golang/v3/scaffolds/init.go @@ -18,13 +18,11 @@ package scaffolds import ( "fmt" - "path/filepath" "github.com/spf13/afero" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" - "sigs.k8s.io/kubebuilder/v3/pkg/model" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" @@ -33,7 +31,6 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - internalmachinery "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) const ( @@ -63,7 +60,7 @@ type initScaffolder struct { func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaffolder { return &initScaffolder{ config: config, - boilerplatePath: filepath.Join("hack", "boilerplate.go.txt"), + boilerplatePath: hack.DefaultBoilerplatePath, license: license, owner: owner, } @@ -74,25 +71,23 @@ func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs } -func (s *initScaffolder) newUniverse(boilerplate string) *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(boilerplate), - ) -} - // Scaffold implements cmdutil.Scaffolder func (s *initScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - bpFile := &hack.Boilerplate{} + // Initialize the machinery.Scaffold that will write the boilerplate file to disk + // The boilerplate file needs to be scaffolded as a separate step as it is going to + // be used by the rest of the files, even those scaffolded in this command call. + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + bpFile := &hack.Boilerplate{ + License: s.license, + Owner: s.owner, + } bpFile.Path = s.boilerplatePath - bpFile.License = s.license - bpFile.Owner = s.owner - if err := internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(""), - bpFile, - ); err != nil { + if err := scaffold.Execute(bpFile); err != nil { return err } @@ -101,8 +96,13 @@ func (s *initScaffolder) Scaffold() error { return err } - return internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(string(boilerplate)), + // Initialize the machinery.Scaffold that will write the files to disk + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + ) + + return scaffold.Execute( &rbac.Kustomization{}, &rbac.AuthProxyRole{}, &rbac.AuthProxyRoleBinding{}, diff --git a/pkg/plugins/golang/v3/scaffolds/webhook.go b/pkg/plugins/golang/v3/scaffolds/webhook.go index 68bf19647b6..20a1a0ab825 100644 --- a/pkg/plugins/golang/v3/scaffolds/webhook.go +++ b/pkg/plugins/golang/v3/scaffolds/webhook.go @@ -23,7 +23,6 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" - "sigs.k8s.io/kubebuilder/v3/pkg/model" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api" @@ -31,15 +30,13 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - internalmachinery "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) var _ cmdutil.Scaffolder = &webhookScaffolder{} type webhookScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource // fs is the filesystem that will be used by the scaffolder fs machinery.Filesystem @@ -62,24 +59,22 @@ func (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs } -func (s *webhookScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), - ) -} - // Scaffold implements cmdutil.Scaffolder func (s *webhookScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") // Load the boilerplate - bp, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) if err != nil { return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err) } - s.boilerplate = string(bp) + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) // Keep track of these values before the update doDefaulting := s.resource.HasDefaultingWebhook() @@ -90,8 +85,7 @@ func (s *webhookScaffolder) Scaffold() error { return fmt.Errorf("error updating resource: %w", err) } - if err := internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Webhook{Force: s.force}, &templates.MainUpdater{WireWebhook: true}, &kdefault.WebhookCAInjectionPatch{}, @@ -110,8 +104,7 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. if doDefaulting || doValidation { - if err := internalmachinery.NewScaffold(s.fs).Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.WebhookSuite{}, ); err != nil { return err diff --git a/pkg/plugins/internal/filesystem/errors.go b/pkg/plugins/internal/filesystem/errors.go deleted file mode 100644 index 7f605d3241a..00000000000 --- a/pkg/plugins/internal/filesystem/errors.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -Copyright 2020 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 filesystem - -import ( - "errors" - "fmt" -) - -// This file contains the errors returned by the file system wrapper -// They are not exported as they should not be created outside of this package -// Exported functions are provided to check which kind of error was returned - -// fileExistsError is returned if it could not be checked if the file exists -type fileExistsError struct { - path string - err error -} - -// Error implements error interface -func (e fileExistsError) Error() string { - return fmt.Sprintf("failed to check if %s exists: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e fileExistsError) Unwrap() error { - return e.err -} - -// IsFileExistsError checks if the returned error is because the file could not be checked for existence -func IsFileExistsError(err error) bool { - return errors.As(err, &fileExistsError{}) -} - -// openFileError is returned if the file could not be opened -type openFileError struct { - path string - err error -} - -// Error implements error interface -func (e openFileError) Error() string { - return fmt.Sprintf("failed to open %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e openFileError) Unwrap() error { - return e.err -} - -// IsOpenFileError checks if the returned error is because the file could not be opened -func IsOpenFileError(err error) bool { - return errors.As(err, &openFileError{}) -} - -// createDirectoryError is returned if the directory could not be created -type createDirectoryError struct { - path string - err error -} - -// Error implements error interface -func (e createDirectoryError) Error() string { - return fmt.Sprintf("failed to create directory for %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e createDirectoryError) Unwrap() error { - return e.err -} - -// IsCreateDirectoryError checks if the returned error is because the directory could not be created -func IsCreateDirectoryError(err error) bool { - return errors.As(err, &createDirectoryError{}) -} - -// createFileError is returned if the file could not be created -type createFileError struct { - path string - err error -} - -// Error implements error interface -func (e createFileError) Error() string { - return fmt.Sprintf("failed to create %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e createFileError) Unwrap() error { - return e.err -} - -// IsCreateFileError checks if the returned error is because the file could not be created -func IsCreateFileError(err error) bool { - return errors.As(err, &createFileError{}) -} - -// readFileError is returned if the file could not be read -type readFileError struct { - path string - err error -} - -// Error implements error interface -func (e readFileError) Error() string { - return fmt.Sprintf("failed to read from %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e readFileError) Unwrap() error { - return e.err -} - -// IsReadFileError checks if the returned error is because the file could not be read -func IsReadFileError(err error) bool { - return errors.As(err, &readFileError{}) -} - -// writeFileError is returned if the file could not be written -type writeFileError struct { - path string - err error -} - -// Error implements error interface -func (e writeFileError) Error() string { - return fmt.Sprintf("failed to write to %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e writeFileError) Unwrap() error { - return e.err -} - -// IsWriteFileError checks if the returned error is because the file could not be written to -func IsWriteFileError(err error) bool { - return errors.As(err, &writeFileError{}) -} - -// closeFileError is returned if the file could not be created -type closeFileError struct { - path string - err error -} - -// Error implements error interface -func (e closeFileError) Error() string { - return fmt.Sprintf("failed to close %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e closeFileError) Unwrap() error { - return e.err -} - -// IsCloseFileError checks if the returned error is because the file could not be closed -func IsCloseFileError(err error) bool { - return errors.As(err, &closeFileError{}) -} diff --git a/pkg/plugins/internal/filesystem/errors_test.go b/pkg/plugins/internal/filesystem/errors_test.go deleted file mode 100644 index 090101fa4e4..00000000000 --- a/pkg/plugins/internal/filesystem/errors_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 2020 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 filesystem - -import ( - "errors" - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var _ = Describe("Errors", func() { - var ( - path = filepath.Join("path", "to", "file") - err = errors.New("test error") - fileExistsErr = fileExistsError{path, err} - openFileErr = openFileError{path, err} - createDirectoryErr = createDirectoryError{path, err} - createFileErr = createFileError{path, err} - readFileErr = readFileError{path, err} - writeFileErr = writeFileError{path, err} - closeFileErr = closeFileError{path, err} - ) - - DescribeTable("IsXxxxError should return true for themselves and false for the rest", - func(f func(error) bool, itself error, rest ...error) { - Expect(f(itself)).To(BeTrue()) - for _, err := range rest { - Expect(f(err)).To(BeFalse()) - } - }, - Entry("file exists", IsFileExistsError, fileExistsErr, - openFileErr, createDirectoryErr, createFileErr, readFileErr, writeFileErr, closeFileErr), - Entry("open file", IsOpenFileError, openFileErr, - fileExistsErr, createDirectoryErr, createFileErr, readFileErr, writeFileErr, closeFileErr), - Entry("create directory", IsCreateDirectoryError, createDirectoryErr, - fileExistsErr, openFileErr, createFileErr, readFileErr, writeFileErr, closeFileErr), - Entry("create file", IsCreateFileError, createFileErr, - fileExistsErr, openFileErr, createDirectoryErr, readFileErr, writeFileErr, closeFileErr), - Entry("read file", IsReadFileError, readFileErr, - fileExistsErr, openFileErr, createDirectoryErr, createFileErr, writeFileErr, closeFileErr), - Entry("write file", IsWriteFileError, writeFileErr, - fileExistsErr, openFileErr, createDirectoryErr, createFileErr, readFileErr, closeFileErr), - Entry("close file", IsCloseFileError, closeFileErr, - fileExistsErr, openFileErr, createDirectoryErr, createFileErr, readFileErr, writeFileErr), - ) - - DescribeTable("should contain the wrapped error and error message", - func(err error) { - Expect(err).To(MatchError(err)) - Expect(err.Error()).To(ContainSubstring(err.Error())) - }, - Entry("file exists", fileExistsErr), - Entry("open file", openFileErr), - Entry("create directory", createDirectoryErr), - Entry("create file", createFileErr), - Entry("read file", readFileErr), - Entry("write file", writeFileErr), - Entry("close file", closeFileErr), - ) -}) diff --git a/pkg/plugins/internal/filesystem/filesystem.go b/pkg/plugins/internal/filesystem/filesystem.go deleted file mode 100644 index 26cf4ba31ef..00000000000 --- a/pkg/plugins/internal/filesystem/filesystem.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -Copyright 2020 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 filesystem - -import ( - "io" - "os" - "path/filepath" - - "github.com/spf13/afero" -) - -const ( - createOrUpdate = os.O_WRONLY | os.O_CREATE | os.O_TRUNC - - defaultDirectoryPermission os.FileMode = 0700 - defaultFilePermission os.FileMode = 0600 -) - -// FileSystem is an IO wrapper to create files -type FileSystem interface { - // Exists checks if the file exists - Exists(path string) (bool, error) - - // Open opens the file and returns a self-closing io.Reader. - Open(path string) (io.ReadCloser, error) - - // Create creates the directory and file and returns a self-closing - // io.Writer pointing to that file. If the file exists, it truncates it. - Create(path string) (io.Writer, error) -} - -// fileSystem implements FileSystem -type fileSystem struct { - fs afero.Fs - dirPerm os.FileMode - filePerm os.FileMode - fileMode int -} - -// New returns a new FileSystem -func New(underlying afero.Fs, options ...Options) FileSystem { - // Default values - fs := fileSystem{ - fs: underlying, - dirPerm: defaultDirectoryPermission, - filePerm: defaultFilePermission, - fileMode: createOrUpdate, - } - - // Apply options - for _, option := range options { - option(&fs) - } - - return fs -} - -// Options configure FileSystem -type Options func(system *fileSystem) - -// DirectoryPermissions makes FileSystem.Create use the provided directory -// permissions -func DirectoryPermissions(dirPerm os.FileMode) Options { - return func(fs *fileSystem) { - fs.dirPerm = dirPerm - } -} - -// FilePermissions makes FileSystem.Create use the provided file permissions -func FilePermissions(filePerm os.FileMode) Options { - return func(fs *fileSystem) { - fs.filePerm = filePerm - } -} - -// Exists implements FileSystem.Exists -func (fs fileSystem) Exists(path string) (bool, error) { - exists, err := afero.Exists(fs.fs, path) - if err != nil { - return exists, fileExistsError{path, err} - } - - return exists, nil -} - -// Open implements FileSystem.Open -func (fs fileSystem) Open(path string) (io.ReadCloser, error) { - rc, err := fs.fs.Open(path) - if err != nil { - return nil, openFileError{path, err} - } - - return &readFile{path, rc}, nil -} - -// Create implements FileSystem.Create -func (fs fileSystem) Create(path string) (io.Writer, error) { - // Create the directory if needed - if err := fs.fs.MkdirAll(filepath.Dir(path), fs.dirPerm); err != nil { - return nil, createDirectoryError{path, err} - } - - // Create or truncate the file - wc, err := fs.fs.OpenFile(path, fs.fileMode, fs.filePerm) - if err != nil { - return nil, createFileError{path, err} - } - - return &writeFile{path, wc}, nil -} - -var _ io.ReadCloser = &readFile{} - -// readFile implements io.Reader -type readFile struct { - path string - io.ReadCloser -} - -// Read implements io.Reader.ReadCloser -func (f *readFile) Read(content []byte) (n int, err error) { - // Read the content - n, err = f.ReadCloser.Read(content) - // EOF is a special case error that we can't wrap - if err == io.EOF { - return - } - if err != nil { - return n, readFileError{f.path, err} - } - - return n, nil -} - -// Close implements io.Reader.ReadCloser -func (f *readFile) Close() error { - if err := f.ReadCloser.Close(); err != nil { - return closeFileError{f.path, err} - } - - return nil -} - -// writeFile implements io.Writer -type writeFile struct { - path string - io.WriteCloser -} - -// Write implements io.Writer.Write -func (f *writeFile) Write(content []byte) (n int, err error) { - // Close the file when we end writing - defer func() { - if closeErr := f.Close(); err == nil && closeErr != nil { - err = closeFileError{f.path, err} - } - }() - - // Write the content - n, err = f.WriteCloser.Write(content) - if err != nil { - return n, writeFileError{f.path, err} - } - - return n, nil -} diff --git a/pkg/plugins/internal/filesystem/filesystem_suite_test.go b/pkg/plugins/internal/filesystem/filesystem_suite_test.go deleted file mode 100644 index 4d1750e2821..00000000000 --- a/pkg/plugins/internal/filesystem/filesystem_suite_test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -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 filesystem - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestFilesystem(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Filesystem suite") -} diff --git a/pkg/plugins/internal/filesystem/filesystem_test.go b/pkg/plugins/internal/filesystem/filesystem_test.go deleted file mode 100644 index c86b08426b2..00000000000 --- a/pkg/plugins/internal/filesystem/filesystem_test.go +++ /dev/null @@ -1,151 +0,0 @@ -/* -Copyright 2020 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 filesystem - -import ( - "os" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/spf13/afero" -) - -var _ = Describe("FileSystem", func() { - Describe("New", func() { - const ( - dirPerm os.FileMode = 0777 - filePerm os.FileMode = 0666 - ) - - var ( - fsi FileSystem - fs fileSystem - ok bool - ) - - Context("when using no options", func() { - BeforeEach(func() { - fsi = New(afero.NewMemMapFs()) - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use default directory permission", func() { - Expect(fs.dirPerm).To(Equal(defaultDirectoryPermission)) - }) - - It("should use default file permission", func() { - Expect(fs.filePerm).To(Equal(defaultFilePermission)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - - Context("when using directory permission option", func() { - BeforeEach(func() { - fsi = New(afero.NewMemMapFs(), DirectoryPermissions(dirPerm)) - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use provided directory permission", func() { - Expect(fs.dirPerm).To(Equal(dirPerm)) - }) - - It("should use default file permission", func() { - Expect(fs.filePerm).To(Equal(defaultFilePermission)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - - Context("when using file permission option", func() { - BeforeEach(func() { - fsi = New(afero.NewMemMapFs(), FilePermissions(filePerm)) - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use default directory permission", func() { - Expect(fs.dirPerm).To(Equal(defaultDirectoryPermission)) - }) - - It("should use provided file permission", func() { - Expect(fs.filePerm).To(Equal(filePerm)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - - Context("when using both directory and file permission options", func() { - BeforeEach(func() { - fsi = New(afero.NewMemMapFs(), DirectoryPermissions(dirPerm), FilePermissions(filePerm)) - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use provided directory permission", func() { - Expect(fs.dirPerm).To(Equal(dirPerm)) - }) - - It("should use provided file permission", func() { - Expect(fs.filePerm).To(Equal(filePerm)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - }) - - // NOTE: FileSystem.Exists, FileSystem.Open, FileSystem.Open().Read, FileSystem.Create and FileSystem.Create().Write - // are hard to test in unitary tests as they deal with actual files -}) diff --git a/pkg/plugins/internal/filesystem/mock.go b/pkg/plugins/internal/filesystem/mock.go deleted file mode 100644 index b7d213c1fc3..00000000000 --- a/pkg/plugins/internal/filesystem/mock.go +++ /dev/null @@ -1,217 +0,0 @@ -/* -Copyright 2020 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 filesystem - -import ( - "bytes" - "io" -) - -// mockFileSystem implements FileSystem -type mockFileSystem struct { - path string - exists func(path string) bool - existsError error - openFileError error - createDirError error - createFileError error - input *bytes.Buffer - readFileError error - output *bytes.Buffer - writeFileError error - closeFileError error -} - -// NewMock returns a new FileSystem -func NewMock(options ...MockOptions) FileSystem { - // Default values - fs := mockFileSystem{ - exists: func(_ string) bool { return false }, - output: new(bytes.Buffer), - } - - // Apply options - for _, option := range options { - option(&fs) - } - - return fs -} - -// MockOptions configure FileSystem -type MockOptions func(system *mockFileSystem) - -// MockPath ensures that the file created with this scaffold is at path -func MockPath(path string) MockOptions { - return func(fs *mockFileSystem) { - fs.path = path - } -} - -// MockExists makes FileSystem.Exists use the provided function to check if the file exists -func MockExists(exists func(path string) bool) MockOptions { - return func(fs *mockFileSystem) { - fs.exists = exists - } -} - -// MockExistsError makes FileSystem.Exists return err -func MockExistsError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.existsError = err - } -} - -// MockOpenFileError makes FileSystem.Open return err -func MockOpenFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.openFileError = err - } -} - -// MockCreateDirError makes FileSystem.Create return err -func MockCreateDirError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.createDirError = err - } -} - -// MockCreateFileError makes FileSystem.Create return err -func MockCreateFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.createFileError = err - } -} - -// MockInput provides a buffer where the content will be read from -func MockInput(input *bytes.Buffer) MockOptions { - return func(fs *mockFileSystem) { - fs.input = input - } -} - -// MockReadFileError makes the Read method (of the io.Reader returned by FileSystem.Open) return err -func MockReadFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.readFileError = err - } -} - -// MockOutput provides a buffer where the content will be written -func MockOutput(output *bytes.Buffer) MockOptions { - return func(fs *mockFileSystem) { - fs.output = output - } -} - -// MockWriteFileError makes the Write method (of the io.Writer returned by FileSystem.Create) return err -func MockWriteFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.writeFileError = err - } -} - -// MockCloseFileError makes the Write method (of the io.Writer returned by FileSystem.Create) return err -func MockCloseFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.closeFileError = err - } -} - -// Exists implements FileSystem.Exists -func (fs mockFileSystem) Exists(path string) (bool, error) { - if fs.existsError != nil { - return false, fileExistsError{path, fs.existsError} - } - - return fs.exists(path), nil -} - -// Open implements FileSystem.Open -func (fs mockFileSystem) Open(path string) (io.ReadCloser, error) { - if fs.openFileError != nil { - return nil, openFileError{path, fs.openFileError} - } - - if fs.input == nil { - fs.input = bytes.NewBufferString("Hello world!") - } - - return &mockReadFile{path, fs.input, fs.readFileError, fs.closeFileError}, nil -} - -// Create implements FileSystem.Create -func (fs mockFileSystem) Create(path string) (io.Writer, error) { - if fs.createDirError != nil { - return nil, createDirectoryError{path, fs.createDirError} - } - - if fs.createFileError != nil { - return nil, createFileError{path, fs.createFileError} - } - - return &mockWriteFile{path, fs.output, fs.writeFileError, fs.closeFileError}, nil -} - -// mockReadFile implements io.Reader mocking a readFile for tests -type mockReadFile struct { - path string - input *bytes.Buffer - readFileError error - closeFileError error -} - -// Read implements io.Reader.ReadCloser -func (f *mockReadFile) Read(content []byte) (n int, err error) { - if f.readFileError != nil { - return 0, readFileError{path: f.path, err: f.readFileError} - } - - return f.input.Read(content) -} - -// Read implements io.Reader.ReadCloser -func (f *mockReadFile) Close() error { - if f.closeFileError != nil { - return closeFileError{path: f.path, err: f.closeFileError} - } - - return nil -} - -// mockWriteFile implements io.Writer mocking a writeFile for tests -type mockWriteFile struct { - path string - content *bytes.Buffer - writeFileError error - closeFileError error -} - -// Write implements io.Writer.Write -func (f *mockWriteFile) Write(content []byte) (n int, err error) { - defer func() { - if err == nil && f.closeFileError != nil { - err = closeFileError{f.path, f.closeFileError} - } - }() - - if f.writeFileError != nil { - return 0, writeFileError{f.path, f.writeFileError} - } - - return f.content.Write(content) -} diff --git a/pkg/plugins/internal/filesystem/mock_test.go b/pkg/plugins/internal/filesystem/mock_test.go deleted file mode 100644 index e9e7d58ccbf..00000000000 --- a/pkg/plugins/internal/filesystem/mock_test.go +++ /dev/null @@ -1,448 +0,0 @@ -/* -Copyright 2020 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 filesystem - -import ( - "bytes" - "errors" - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -//nolint:dupl -var _ = Describe("MockFileSystem", func() { - var ( - fsi FileSystem - fs mockFileSystem - ok bool - options []MockOptions - testErr = errors.New("test error") - ) - - JustBeforeEach(func() { - fsi = NewMock(options...) - fs, ok = fsi.(mockFileSystem) - }) - - Context("when using no options", func() { - BeforeEach(func() { - options = make([]MockOptions, 0) - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockPath", func() { - var filePath = filepath.Join("path", "to", "file") - - BeforeEach(func() { - options = []MockOptions{MockPath(filePath)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should save the provided path", func() { - Expect(fs.path).To(Equal(filePath)) - }) - }) - - Context("when using MockExists", func() { - BeforeEach(func() { - options = []MockOptions{MockExists(func(_ string) bool { return true })} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeTrue()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockExistsError", func() { - BeforeEach(func() { - options = []MockOptions{MockExistsError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should error when calling Exists", func() { - _, err := fsi.Exists("") - Expect(err).To(MatchError(testErr)) - Expect(IsFileExistsError(err)).To(BeTrue()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockOpenFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockOpenFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should error when calling Open", func() { - _, err := fsi.Open("") - Expect(err).To(MatchError(testErr)) - Expect(IsOpenFileError(err)).To(BeTrue()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockCreateDirError", func() { - BeforeEach(func() { - options = []MockOptions{MockCreateDirError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should error when calling Create", func() { - _, err := fsi.Create("") - Expect(err).To(MatchError(testErr)) - Expect(IsCreateDirectoryError(err)).To(BeTrue()) - }) - }) - - Context("when using MockCreateFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockCreateFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should error when calling Create", func() { - _, err := fsi.Create("") - Expect(err).To(MatchError(testErr)) - Expect(IsCreateFileError(err)).To(BeTrue()) - }) - }) - - Context("when using MockInput", func() { - var ( - input *bytes.Buffer - fileContent = []byte("Hello world!") - ) - - BeforeEach(func() { - input = bytes.NewBufferString("Hello world!") - options = []MockOptions{MockInput(input)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files and the content to be accessible", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - output := make([]byte, len(fileContent)) - n, err := f.Read(output) - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(len(fileContent))) - Expect(output).To(Equal(fileContent)) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockReadFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockReadFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should error when calling Open().Read", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - output := make([]byte, 0) - _, err = f.Read(output) - Expect(err).To(MatchError(testErr)) - Expect(IsReadFileError(err)).To(BeTrue()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockOutput", func() { - var ( - output bytes.Buffer - fileContent = []byte("Hello world!") - ) - - BeforeEach(func() { - options = []MockOptions{MockOutput(&output)} - output.Reset() - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files and the content should be accesible", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - n, err := f.Write(fileContent) - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(len(fileContent))) - Expect(output.Bytes()).To(Equal(fileContent)) - }) - }) - - Context("when using MockWriteFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockWriteFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should error when calling Create().Write", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).To(MatchError(testErr)) - Expect(IsWriteFileError(err)).To(BeTrue()) - }) - }) - - Context("when using MockCloseFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockCloseFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should error when calling Open().Close", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - err = f.Close() - Expect(err).To(MatchError(testErr)) - Expect(IsCloseFileError(err)).To(BeTrue()) - }) - - It("should error when calling Create().Write", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).To(MatchError(testErr)) - Expect(IsCloseFileError(err)).To(BeTrue()) - }) - }) -}) diff --git a/pkg/plugins/internal/machinery/errors.go b/pkg/plugins/internal/machinery/errors.go deleted file mode 100644 index faba57a1d05..00000000000 --- a/pkg/plugins/internal/machinery/errors.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2020 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 machinery - -import ( - "errors" - "fmt" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -// This file contains the errors returned by the scaffolding machinery -// They are not exported as they should not be created outside of this package -// Exported functions are provided to check which kind of error was returned - -// fileAlreadyExistsError is returned if the file is expected not to exist but it does -type fileAlreadyExistsError struct { - path string -} - -// Error implements error interface -func (e fileAlreadyExistsError) Error() string { - return fmt.Sprintf("failed to create %s: file already exists", e.path) -} - -// IsFileAlreadyExistsError checks if the returned error is because the file already existed when expected not to -func IsFileAlreadyExistsError(err error) bool { - return errors.As(err, &fileAlreadyExistsError{}) -} - -// modelAlreadyExistsError is returned if the file is expected not to exist but a previous model does -type modelAlreadyExistsError struct { - path string -} - -// Error implements error interface -func (e modelAlreadyExistsError) Error() string { - return fmt.Sprintf("failed to create %s: model already exists", e.path) -} - -// IsModelAlreadyExistsError checks if the returned error is because the model already existed when expected not to -func IsModelAlreadyExistsError(err error) bool { - return errors.As(err, &modelAlreadyExistsError{}) -} - -// unknownIfExistsActionError is returned if the if-exists-action is unknown -type unknownIfExistsActionError struct { - path string - ifExistsAction file.IfExistsAction -} - -// Error implements error interface -func (e unknownIfExistsActionError) Error() string { - return fmt.Sprintf("unknown behavior if file exists (%d) for %s", e.ifExistsAction, e.path) -} - -// IsUnknownIfExistsActionError checks if the returned error is because the if-exists-action is unknown -func IsUnknownIfExistsActionError(err error) bool { - return errors.As(err, &unknownIfExistsActionError{}) -} diff --git a/pkg/plugins/internal/machinery/errors_test.go b/pkg/plugins/internal/machinery/errors_test.go deleted file mode 100644 index 3d6b8ce14e4..00000000000 --- a/pkg/plugins/internal/machinery/errors_test.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2020 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 machinery - -import ( - "errors" - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var _ = Describe("Errors", func() { - var ( - path = filepath.Join("path", "to", "file") - err = errors.New("test error") - fileAlreadyExistsErr = fileAlreadyExistsError{path} - modelAlreadyExistsErr = modelAlreadyExistsError{path} - unknownIfExistsActionErr = unknownIfExistsActionError{path, -1} - ) - - DescribeTable("IsXxxxError should return true for themselves and false for the rest", - func(f func(error) bool, itself error, rest ...error) { - Expect(f(itself)).To(BeTrue()) - for _, err := range rest { - Expect(f(err)).To(BeFalse()) - } - }, - Entry("file exists", IsFileAlreadyExistsError, fileAlreadyExistsErr, - err, modelAlreadyExistsErr, unknownIfExistsActionErr), - Entry("model exists", IsModelAlreadyExistsError, modelAlreadyExistsErr, - err, fileAlreadyExistsErr, unknownIfExistsActionErr), - Entry("unknown if exists action", IsUnknownIfExistsActionError, unknownIfExistsActionErr, - err, fileAlreadyExistsErr, modelAlreadyExistsErr), - ) - - DescribeTable("should contain the wrapped error and error message", - func(err error) { - Expect(err).To(MatchError(err)) - Expect(err.Error()).To(ContainSubstring(err.Error())) - }, - ) - - // NOTE: the following test increases coverage - It("should print a descriptive error message", func() { - Expect(fileAlreadyExistsErr.Error()).To(ContainSubstring("file already exists")) - Expect(modelAlreadyExistsErr.Error()).To(ContainSubstring("model already exists")) - Expect(unknownIfExistsActionErr.Error()).To(ContainSubstring("unknown behavior if file exists")) - }) -}) diff --git a/pkg/plugins/internal/machinery/scaffold_test.go b/pkg/plugins/internal/machinery/scaffold_test.go deleted file mode 100644 index b4d2ca45814..00000000000 --- a/pkg/plugins/internal/machinery/scaffold_test.go +++ /dev/null @@ -1,557 +0,0 @@ -/* -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 machinery - -import ( - "bytes" - "errors" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "github.com/spf13/afero" - - "sigs.k8s.io/kubebuilder/v3/pkg/machinery" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/filesystem" -) - -var _ = Describe("Scaffold", func() { - Describe("NewScaffold", func() { - var ( - si Scaffold - s *scaffold - ok bool - ) - - Context("when using no plugins", func() { - BeforeEach(func() { - si = NewScaffold(machinery.Filesystem{FS: afero.NewMemMapFs()}) - s, ok = si.(*scaffold) - }) - - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) - - It("should not have any plugin", func() { - Expect(len(s.plugins)).To(Equal(0)) - }) - }) - - Context("when using one plugin", func() { - BeforeEach(func() { - si = NewScaffold(machinery.Filesystem{FS: afero.NewMemMapFs()}, fakePlugin{}) - s, ok = si.(*scaffold) - }) - - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) - - It("should have one plugin", func() { - Expect(len(s.plugins)).To(Equal(1)) - }) - }) - - Context("when using several plugins", func() { - BeforeEach(func() { - si = NewScaffold(machinery.Filesystem{FS: afero.NewMemMapFs()}, - fakePlugin{}, fakePlugin{}, fakePlugin{}) - s, ok = si.(*scaffold) - }) - - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) - - It("should have several plugins", func() { - Expect(len(s.plugins)).To(Equal(3)) - }) - }) - }) - - Describe("Scaffold.Execute", func() { - const fileContent = "Hello world!" - - var ( - output bytes.Buffer - testErr = errors.New("error text") - ) - - BeforeEach(func() { - output.Reset() - }) - - DescribeTable("successes", - func(expected string, files ...file.Builder) { - s := &scaffold{ - fs: filesystem.NewMock( - filesystem.MockOutput(&output), - ), - } - - Expect(s.Execute(model.NewUniverse(), files...)).To(Succeed()) - Expect(output.String()).To(Equal(expected)) - }, - Entry("should write the file", - fileContent, - fakeTemplate{body: fileContent}, - ), - Entry("should skip optional models if already have one", - fileContent, - fakeTemplate{body: fileContent}, - fakeTemplate{}, - ), - Entry("should overwrite required models if already have one", - fileContent, - fakeTemplate{}, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: fileContent}, - ), - Entry("should format a go file", - "package file\n", - fakeTemplate{fakeBuilder: fakeBuilder{path: "file.go"}, body: "package file"}, - ), - ) - - DescribeTable("file builders related errors", - func(f func(error) bool, files ...file.Builder) { - s := &scaffold{fs: filesystem.NewMock()} - - Expect(f(s.Execute(model.NewUniverse(), files...))).To(BeTrue()) - }, - Entry("should fail if unable to validate a file builder", - file.IsValidateError, - fakeRequiresValidation{validateErr: testErr}, - ), - Entry("should fail if unable to set default values for a template", - file.IsSetTemplateDefaultsError, - fakeTemplate{err: testErr}, - ), - Entry("should fail if an unexpected previous model is found", - IsModelAlreadyExistsError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename"}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}}, - ), - Entry("should fail if behavior if file exists is not defined", - IsUnknownIfExistsActionError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename"}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, - ), - ) - - // Following errors are unwrapped, so we need to check for substrings - DescribeTable("template related errors", - func(errMsg string, files ...file.Builder) { - s := &scaffold{fs: filesystem.NewMock()} - - err := s.Execute(model.NewUniverse(), files...) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(errMsg)) - }, - Entry("should fail if a template is broken", - "template: ", - fakeTemplate{body: "{{ .Field }"}, - ), - Entry("should fail if a template params aren't provided", - "template: ", - fakeTemplate{body: "{{ .Field }}"}, - ), - Entry("should fail if unable to format a go file", - "expected 'package', found ", - fakeTemplate{fakeBuilder: fakeBuilder{path: "file.go"}, body: fileContent}, - ), - ) - - DescribeTable("insert strings", - func(input, expected string, files ...file.Builder) { - s := &scaffold{ - fs: filesystem.NewMock( - filesystem.MockInput(bytes.NewBufferString(input)), - filesystem.MockOutput(&output), - filesystem.MockExists(func(_ string) bool { return len(input) != 0 }), - ), - } - - Expect(s.Execute(model.NewUniverse(), files...)).To(Succeed()) - Expect(output.String()).To(Equal(expected)) - }, - Entry("should insert lines for go files", - ` -//+kubebuilder:scaffold:- -`, - ` -1 -2 -//+kubebuilder:scaffold:- -`, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, - }, - ), - Entry("should insert lines for yaml files", - ` -#+kubebuilder:scaffold:- -`, - ` -1 -2 -#+kubebuilder:scaffold:- -`, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.yaml", "-"): {"1\n", "2\n"}}, - }, - ), - Entry("should use models if there is no file", - "", - ` -1 -2 -//+kubebuilder:scaffold:- -`, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: ` -//+kubebuilder:scaffold:- -`}, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, - }, - ), - Entry("should use required models over files", - fileContent, - ` -1 -2 -//+kubebuilder:scaffold:- -`, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: ` -//+kubebuilder:scaffold:- -`}, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, - }, - ), - Entry("should use files over optional models", - ` -//+kubebuilder:scaffold:- -`, - ` -1 -2 -//+kubebuilder:scaffold:- -`, - fakeTemplate{body: fileContent}, - fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, - }, - }, - ), - Entry("should filter invalid markers", - ` -//+kubebuilder:scaffold:- -//+kubebuilder:scaffold:* -`, - ` -1 -2 -//+kubebuilder:scaffold:- -//+kubebuilder:scaffold:* -`, - fakeInserter{ - markers: []file.Marker{file.NewMarkerFor("file.go", "-")}, - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, - file.NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, - }, - }, - ), - Entry("should filter already existing one-line code fragments", - ` -1 -//+kubebuilder:scaffold:- -3 -4 -//+kubebuilder:scaffold:* -`, - ` -1 -2 -//+kubebuilder:scaffold:- -3 -4 -//+kubebuilder:scaffold:* -`, - fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, - file.NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, - }, - }, - ), - Entry("should not insert anything if no code fragment", - "", // input is provided through a template as mock fs doesn't copy it to the output buffer if no-op - ` -//+kubebuilder:scaffold:- -`, - fakeTemplate{body: ` -//+kubebuilder:scaffold:- -`}, - fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {}, - }, - }, - ), - ) - - DescribeTable("insert strings related errors", - func(f func(error) bool, files ...file.Builder) { - s := &scaffold{ - fs: filesystem.NewMock( - filesystem.MockExists(func(_ string) bool { return true }), - ), - } - - err := s.Execute(model.NewUniverse(), files...) - Expect(err).To(HaveOccurred()) - Expect(f(err)).To(BeTrue()) - }, - Entry("should fail if inserting into a model that fails when a file exists and it does exist", - IsFileAlreadyExistsError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}}, - fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, - ), - Entry("should fail if inserting into a model with unknown behavior if the file exists and it does exist", - IsUnknownIfExistsActionError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, - fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, - ), - ) - - It("should fail if a plugin fails", func() { - s := &scaffold{ - fs: filesystem.NewMock(), - plugins: []model.Plugin{fakePlugin{err: testErr}}, - } - - err := s.Execute( - model.NewUniverse(), - fakeTemplate{}, - ) - Expect(err).To(MatchError(testErr)) - Expect(model.IsPluginError(err)).To(BeTrue()) - }) - - Context("write when the file already exists", func() { - var s Scaffold - - BeforeEach(func() { - s = &scaffold{ - fs: filesystem.NewMock( - filesystem.MockExists(func(_ string) bool { return true }), - filesystem.MockOutput(&output), - ), - } - }) - - It("should skip the file by default", func() { - Expect(s.Execute( - model.NewUniverse(), - fakeTemplate{body: fileContent}, - )).To(Succeed()) - Expect(output.String()).To(BeEmpty()) - }) - - It("should write the file if configured to do so", func() { - Expect(s.Execute( - model.NewUniverse(), - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: fileContent}, - )).To(Succeed()) - Expect(output.String()).To(Equal(fileContent)) - }) - - It("should error if configured to do so", func() { - err := s.Execute( - model.NewUniverse(), - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}, body: fileContent}, - ) - Expect(err).To(HaveOccurred()) - Expect(IsFileAlreadyExistsError(err)).To(BeTrue()) - Expect(output.String()).To(BeEmpty()) - }) - }) - - DescribeTable("filesystem errors", - func( - mockErrorF func(error) filesystem.MockOptions, - checkErrorF func(error) bool, - files ...file.Builder, - ) { - s := &scaffold{ - fs: filesystem.NewMock( - mockErrorF(testErr), - ), - } - - err := s.Execute(model.NewUniverse(), files...) - Expect(err).To(HaveOccurred()) - Expect(checkErrorF(err)).To(BeTrue()) - }, - Entry("should fail if fs.Exists failed (at file writing)", - filesystem.MockExistsError, filesystem.IsFileExistsError, - fakeTemplate{}, - ), - Entry("should fail if fs.Exists failed (at model updating)", - filesystem.MockExistsError, filesystem.IsFileExistsError, - fakeTemplate{}, - fakeInserter{}, - ), - Entry("should fail if fs.Open was unable to open the file", - filesystem.MockOpenFileError, filesystem.IsOpenFileError, - fakeInserter{}, - ), - Entry("should fail if fs.Open().Read was unable to read the file", - filesystem.MockReadFileError, filesystem.IsReadFileError, - fakeInserter{}, - ), - Entry("should fail if fs.Open().Close was unable to close the file", - filesystem.MockCloseFileError, filesystem.IsCloseFileError, - fakeInserter{}, - ), - Entry("should fail if fs.Create was unable to create the directory", - filesystem.MockCreateDirError, filesystem.IsCreateDirectoryError, - fakeTemplate{}, - ), - Entry("should fail if fs.Create was unable to create the file", - filesystem.MockCreateFileError, filesystem.IsCreateFileError, - fakeTemplate{}, - ), - Entry("should fail if fs.Create().Write was unable to write the file", - filesystem.MockWriteFileError, filesystem.IsWriteFileError, - fakeTemplate{}, - ), - Entry("should fail if fs.Create().Write was unable to close the file", - filesystem.MockCloseFileError, filesystem.IsCloseFileError, - fakeTemplate{}, - ), - ) - }) -}) - -var _ model.Plugin = fakePlugin{} - -// fakePlugin is used to mock a model.Plugin in order to test Scaffold -type fakePlugin struct { - err error -} - -// Pipe implements model.Plugin -func (f fakePlugin) Pipe(_ *model.Universe) error { - return f.err -} - -var _ file.Builder = fakeBuilder{} - -// fakeBuilder is used to mock a file.Builder -type fakeBuilder struct { - path string - ifExistsAction file.IfExistsAction -} - -// GetPath implements file.Builder -func (f fakeBuilder) GetPath() string { - return f.path -} - -// GetIfExistsAction implements file.Builder -func (f fakeBuilder) GetIfExistsAction() file.IfExistsAction { - return f.ifExistsAction -} - -var _ file.RequiresValidation = fakeRequiresValidation{} - -// fakeRequiresValidation is used to mock a file.RequiresValidation in order to test Scaffold -type fakeRequiresValidation struct { - fakeBuilder - - validateErr error -} - -// Validate implements file.RequiresValidation -func (f fakeRequiresValidation) Validate() error { - return f.validateErr -} - -var _ file.Template = fakeTemplate{} - -// fakeTemplate is used to mock a file.File in order to test Scaffold -type fakeTemplate struct { - fakeBuilder - - body string - err error -} - -// GetBody implements file.Template -func (f fakeTemplate) GetBody() string { - return f.body -} - -// SetTemplateDefaults implements file.Template -func (f fakeTemplate) SetTemplateDefaults() error { - if f.err != nil { - return f.err - } - - return nil -} - -type fakeInserter struct { - fakeBuilder - - markers []file.Marker - codeFragments file.CodeFragmentsMap -} - -// GetMarkers implements file.UpdatableTemplate -func (f fakeInserter) GetMarkers() []file.Marker { - if f.markers != nil { - return f.markers - } - - markers := make([]file.Marker, 0, len(f.codeFragments)) - for marker := range f.codeFragments { - markers = append(markers, marker) - } - return markers -} - -// GetCodeFragments implements file.UpdatableTemplate -func (f fakeInserter) GetCodeFragments() file.CodeFragmentsMap { - return f.codeFragments -}