github.com/theishshah/operator-sdk@v0.6.0/pkg/scaffold/scaffold.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Modified from github.com/kubernetes-sigs/controller-tools/pkg/scaffold/scaffold.go 16 17 package scaffold 18 19 import ( 20 "bytes" 21 "fmt" 22 "io" 23 "os" 24 "path/filepath" 25 "strings" 26 "text/template" 27 28 "github.com/operator-framework/operator-sdk/internal/util/fileutil" 29 "github.com/operator-framework/operator-sdk/pkg/scaffold/input" 30 31 log "github.com/sirupsen/logrus" 32 "github.com/spf13/afero" 33 "golang.org/x/tools/imports" 34 ) 35 36 // Scaffold writes Templates to scaffold new files 37 type Scaffold struct { 38 // Repo is the go project package 39 Repo string 40 // AbsProjectPath is the absolute path to the project root, including the project directory. 41 AbsProjectPath string 42 // ProjectName is the operator's name, ex. app-operator 43 ProjectName string 44 // Fs is the filesystem GetWriter uses to write scaffold files. 45 Fs afero.Fs 46 // GetWriter returns a writer for writing scaffold files. 47 GetWriter func(path string, mode os.FileMode) (io.Writer, error) 48 } 49 50 func (s *Scaffold) setFieldsAndValidate(t input.File) error { 51 if b, ok := t.(input.Repo); ok { 52 b.SetRepo(s.Repo) 53 } 54 if b, ok := t.(input.AbsProjectPath); ok { 55 b.SetAbsProjectPath(s.AbsProjectPath) 56 } 57 if b, ok := t.(input.ProjectName); ok { 58 b.SetProjectName(s.ProjectName) 59 } 60 61 // Validate the template is ok 62 if v, ok := t.(input.Validate); ok { 63 if err := v.Validate(); err != nil { 64 return err 65 } 66 } 67 return nil 68 } 69 70 func (s *Scaffold) configure(cfg *input.Config) { 71 s.Repo = cfg.Repo 72 s.AbsProjectPath = cfg.AbsProjectPath 73 s.ProjectName = cfg.ProjectName 74 } 75 76 // Execute executes scaffolding the Files 77 func (s *Scaffold) Execute(cfg *input.Config, files ...input.File) error { 78 if s.Fs == nil { 79 s.Fs = afero.NewOsFs() 80 } 81 if s.GetWriter == nil { 82 s.GetWriter = fileutil.NewFileWriterFS(s.Fs).WriteCloser 83 } 84 85 // Configure s using common fields from cfg. 86 s.configure(cfg) 87 88 for _, f := range files { 89 if err := s.doFile(f); err != nil { 90 return err 91 } 92 } 93 return nil 94 } 95 96 // doFile scaffolds a single file 97 func (s *Scaffold) doFile(e input.File) error { 98 // Set common fields 99 err := s.setFieldsAndValidate(e) 100 if err != nil { 101 return err 102 } 103 104 // Get the template input params 105 i, err := e.GetInput() 106 if err != nil { 107 return err 108 } 109 110 // Ensure we use the absolute file path; i.Path is relative to the project root. 111 absFilePath := filepath.Join(s.AbsProjectPath, i.Path) 112 113 // Check if the file to write already exists 114 if _, err := s.Fs.Stat(absFilePath); err == nil || os.IsExist(err) { 115 switch i.IfExistsAction { 116 case input.Overwrite: 117 case input.Skip: 118 return nil 119 case input.Error: 120 return fmt.Errorf("%s already exists", absFilePath) 121 } 122 } 123 124 return s.doRender(i, e, absFilePath) 125 } 126 127 const goFileExt = ".go" 128 129 func (s *Scaffold) doRender(i input.Input, e input.File, absPath string) error { 130 var mode os.FileMode = fileutil.DefaultFileMode 131 if i.IsExec { 132 mode = fileutil.DefaultExecFileMode 133 } 134 f, err := s.GetWriter(absPath, mode) 135 if err != nil { 136 return err 137 } 138 if c, ok := f.(io.Closer); ok { 139 defer func() { 140 if err := c.Close(); err != nil { 141 log.Fatal(err) 142 } 143 }() 144 } 145 146 var b []byte 147 if c, ok := e.(CustomRenderer); ok { 148 c.SetFS(s.Fs) 149 // CustomRenderers have a non-template method of file rendering. 150 if b, err = c.CustomRender(); err != nil { 151 return err 152 } 153 } else { 154 // All other files are rendered via their templates. 155 temp, err := newTemplate(i) 156 if err != nil { 157 return err 158 } 159 160 out := &bytes.Buffer{} 161 if err = temp.Execute(out, e); err != nil { 162 return err 163 } 164 b = out.Bytes() 165 } 166 167 // gofmt the imports 168 if filepath.Ext(absPath) == goFileExt { 169 b, err = imports.Process(absPath, b, nil) 170 if err != nil { 171 return err 172 } 173 } 174 175 // Files being overwritten must be trucated to len 0 so no old bytes remain. 176 if _, err = s.Fs.Stat(absPath); err == nil && i.IfExistsAction == input.Overwrite { 177 if file, ok := f.(afero.File); ok { 178 if err = file.Truncate(0); err != nil { 179 return err 180 } 181 } 182 } 183 _, err = f.Write(b) 184 log.Infoln("Created", i.Path) 185 return err 186 } 187 188 // newTemplate returns a new template named by i.Path with common functions and 189 // the input's TemplateFuncs. 190 func newTemplate(i input.Input) (*template.Template, error) { 191 t := template.New(i.Path).Funcs(template.FuncMap{ 192 "title": strings.Title, 193 "lower": strings.ToLower, 194 }) 195 if len(i.TemplateFuncs) > 0 { 196 t.Funcs(i.TemplateFuncs) 197 } 198 return t.Parse(i.TemplateBody) 199 }