github.com/snyk/vervet/v4@v4.27.2/internal/scaffold/scaffold.go (about) 1 package scaffold 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "path/filepath" 9 10 "github.com/ghodss/yaml" 11 12 "github.com/snyk/vervet/v4/internal/files" 13 ) 14 15 // ErrAlreadyInitialized is used when scaffolding is being run on a project that is already setup. 16 var ErrAlreadyInitialized = fmt.Errorf("project files already exist") 17 18 // Scaffold defines a Vervet API project scaffold. 19 type Scaffold struct { 20 dst, src string 21 force bool 22 manifest *Manifest 23 } 24 25 const manifestV1 = "1" 26 27 // Manifest defines the scaffold manifest model. 28 type Manifest struct { 29 Version string 30 31 // Organize contains a mapping of files relative to Scaffold src, to be 32 // copied into dst, relative to dst. Missing intermediate directories will 33 // be created as needed. 34 Organize map[string]string `json:"organize"` 35 } 36 37 // Option defines a functional option that modifies a new Scaffold in the 38 // constructor. 39 type Option func(*Scaffold) 40 41 // Force sets the force flag on a Scaffold, which determines whether existing 42 // destination files will be overwritten. Default is false. 43 func Force(force bool) Option { 44 return func(s *Scaffold) { 45 s.force = force 46 } 47 } 48 49 // New returns a new Scaffold loaded from source directory `src` for operation 50 // on destination directory `dst`. The Scaffold src must contain a 51 // `manifest.yaml` which defines how dst will be provisioned. 52 func New(dst, src string, options ...Option) (*Scaffold, error) { 53 if dst == "" || src == "" { 54 return nil, fmt.Errorf("source and destination are required") 55 } 56 var err error 57 dst, err = filepath.Abs(dst) 58 if err != nil { 59 return nil, err 60 } 61 src, err = filepath.Abs(src) 62 if err != nil { 63 return nil, err 64 } 65 66 manifestPath := filepath.Join(src, "manifest.yaml") 67 contents, err := ioutil.ReadFile(manifestPath) 68 if err != nil { 69 return nil, err 70 } 71 var manifest Manifest 72 err = yaml.Unmarshal(contents, &manifest) 73 if err != nil { 74 return nil, err 75 } 76 err = manifest.validate(src) 77 if err != nil { 78 return nil, err 79 } 80 s := &Scaffold{src: src, dst: dst, manifest: &manifest} 81 for i := range options { 82 options[i](s) 83 } 84 return s, nil 85 } 86 87 // Organize provisions files from the scaffold source into its destination. 88 func (s *Scaffold) Organize() error { 89 for dstItem, srcItem := range s.manifest.Organize { 90 dstPath := filepath.Join(s.dst, dstItem) 91 // If we're not force overwriting, check if files already exist. 92 if !s.force { 93 _, err := os.Stat(dstPath) 94 if err == nil { 95 // Project files already exist. 96 return ErrAlreadyInitialized 97 } 98 if !os.IsNotExist(err) { 99 // Something else went wrong; the file not existing is the desired 100 // state. 101 return err 102 } 103 } 104 srcPath := filepath.Join(s.src, srcItem) 105 err := files.CopyItem(dstPath, srcPath, s.force) 106 if err != nil { 107 return fmt.Errorf("failed to copy %q to %q: %w", srcPath, dstPath, err) 108 } 109 } 110 return nil 111 } 112 113 // Init runs a script called `init` in the scaffold source if present, 114 // in the destination directory. 115 func (s *Scaffold) Init() error { 116 initScript := filepath.Join(s.src, "init") 117 if _, err := os.Stat(initScript); os.IsNotExist(err) { 118 return nil // no init script 119 } else if err != nil { 120 return err 121 } 122 cmd := exec.Command(initScript) 123 cmd.Dir = s.dst 124 cmd.Stdin = os.Stdin 125 cmd.Stdout = os.Stdout 126 cmd.Stderr = os.Stderr 127 err := cmd.Run() 128 if err != nil { 129 return fmt.Errorf("init script failed: %w", err) 130 } 131 return nil 132 } 133 134 func (m *Manifest) validate(src string) error { 135 if m.Version == "" { 136 m.Version = manifestV1 137 } 138 if m.Version != manifestV1 { 139 return fmt.Errorf("unsupported manifest version %q", m.Version) 140 } 141 142 if len(m.Organize) == 0 { 143 return fmt.Errorf("empty manifest") 144 } 145 for _, srcItem := range m.Organize { 146 srcPath := filepath.Join(src, srcItem) 147 if _, err := os.Stat(srcPath); err != nil { 148 return fmt.Errorf("cannot stat source item %q: %w", srcPath, err) 149 } 150 } 151 return nil 152 }