github.com/jmrodri/operator-sdk@v0.5.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 "golang.org/x/tools/imports" 33 ) 34 35 // Scaffold writes Templates to scaffold new files 36 type Scaffold struct { 37 // Repo is the go project package 38 Repo string 39 40 // AbsProjectPath is the absolute path to the project root, including the project directory. 41 AbsProjectPath string 42 43 // ProjectName is the operator's name, ex. app-operator 44 ProjectName string 45 46 GetWriter func(path string, mode os.FileMode) (io.Writer, error) 47 } 48 49 func (s *Scaffold) setFieldsAndValidate(t input.File) error { 50 if b, ok := t.(input.Repo); ok { 51 b.SetRepo(s.Repo) 52 } 53 if b, ok := t.(input.AbsProjectPath); ok { 54 b.SetAbsProjectPath(s.AbsProjectPath) 55 } 56 if b, ok := t.(input.ProjectName); ok { 57 b.SetProjectName(s.ProjectName) 58 } 59 60 // Validate the template is ok 61 if v, ok := t.(input.Validate); ok { 62 if err := v.Validate(); err != nil { 63 return err 64 } 65 } 66 return nil 67 } 68 69 func (s *Scaffold) configure(cfg *input.Config) { 70 s.Repo = cfg.Repo 71 s.AbsProjectPath = cfg.AbsProjectPath 72 s.ProjectName = cfg.ProjectName 73 } 74 75 // Execute executes scaffolding the Files 76 func (s *Scaffold) Execute(cfg *input.Config, files ...input.File) error { 77 if s.GetWriter == nil { 78 s.GetWriter = fileutil.NewFileWriter().WriteCloser 79 } 80 81 // Configure s using common fields from cfg. 82 s.configure(cfg) 83 84 for _, f := range files { 85 if err := s.doFile(f); err != nil { 86 return err 87 } 88 } 89 return nil 90 } 91 92 // doFile scaffolds a single file 93 func (s *Scaffold) doFile(e input.File) error { 94 // Set common fields 95 err := s.setFieldsAndValidate(e) 96 if err != nil { 97 return err 98 } 99 100 // Get the template input params 101 i, err := e.GetInput() 102 if err != nil { 103 return err 104 } 105 106 // Ensure we use the absolute file path; i.Path is relative to the project root. 107 absFilePath := filepath.Join(s.AbsProjectPath, i.Path) 108 109 // Check if the file to write already exists 110 if _, err := os.Stat(absFilePath); err == nil || os.IsExist(err) { 111 switch i.IfExistsAction { 112 case input.Overwrite: 113 case input.Skip: 114 return nil 115 case input.Error: 116 return fmt.Errorf("%s already exists", absFilePath) 117 } 118 } 119 120 return s.doRender(i, e, absFilePath) 121 } 122 123 const goFileExt = ".go" 124 125 func (s *Scaffold) doRender(i input.Input, e input.File, absPath string) error { 126 var mode os.FileMode = fileutil.DefaultFileMode 127 if i.IsExec { 128 mode = fileutil.DefaultExecFileMode 129 } 130 f, err := s.GetWriter(absPath, mode) 131 if err != nil { 132 return err 133 } 134 if c, ok := f.(io.Closer); ok { 135 defer func() { 136 if err := c.Close(); err != nil { 137 log.Fatal(err) 138 } 139 }() 140 } 141 142 var b []byte 143 if c, ok := e.(CustomRenderer); ok { 144 // CustomRenderers have a non-template method of file rendering. 145 if b, err = c.CustomRender(); err != nil { 146 return err 147 } 148 } else { 149 // All other files are rendered via their templates. 150 temp, err := newTemplate(i) 151 if err != nil { 152 return err 153 } 154 155 out := &bytes.Buffer{} 156 if err = temp.Execute(out, e); err != nil { 157 return err 158 } 159 b = out.Bytes() 160 } 161 162 // gofmt the imports 163 if filepath.Ext(absPath) == goFileExt { 164 b, err = imports.Process(absPath, b, nil) 165 if err != nil { 166 return err 167 } 168 } 169 170 // Files being overwritten must be trucated to len 0 so no old bytes remain. 171 if _, err = os.Stat(absPath); err == nil && i.IfExistsAction == input.Overwrite { 172 if err = os.Truncate(absPath, 0); err != nil { 173 return err 174 } 175 } 176 _, err = f.Write(b) 177 log.Infoln("Create", i.Path) 178 return err 179 } 180 181 // newTemplate returns a new template named by i.Path with common functions and 182 // the input's TemplateFuncs. 183 func newTemplate(i input.Input) (*template.Template, error) { 184 t := template.New(i.Path).Funcs(template.FuncMap{ 185 "title": strings.Title, 186 "lower": strings.ToLower, 187 }) 188 if len(i.TemplateFuncs) > 0 { 189 t.Funcs(i.TemplateFuncs) 190 } 191 return t.Parse(i.TemplateBody) 192 }