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  }