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  }