mirror of https://github.com/knative/func.git
230 lines
5.5 KiB
Go
230 lines
5.5 KiB
Go
package function
|
|
|
|
// Updating Templates:
|
|
// See documentation in ./templates/README.md
|
|
// go get github.com/markbates/pkger
|
|
//go:generate pkger
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/markbates/pkger"
|
|
)
|
|
|
|
// fileAccessor encapsulates methods for accessing template files.
|
|
type fileAccessor interface {
|
|
Stat(name string) (os.FileInfo, error)
|
|
Open(p string) (file, error)
|
|
}
|
|
|
|
type file interface {
|
|
Readdir(int) ([]os.FileInfo, error)
|
|
Read([]byte) (int, error)
|
|
Close() error
|
|
}
|
|
|
|
// When pkger is run, code analysis detects this Include statement,
|
|
// triggering the serialization of the templates directory and all
|
|
// its contents into pkged.go, which is then made available via
|
|
// a pkger fileAccessor.
|
|
// Path is relative to the go module root.
|
|
func init() {
|
|
_ = pkger.Include("/templates")
|
|
}
|
|
|
|
type templateWriter struct {
|
|
// Extensible Template Repositories
|
|
// templates on disk (extensible templates)
|
|
// Stored on disk at path:
|
|
// [customTemplatesPath]/[repository]/[runtime]/[template]
|
|
// For example
|
|
// ~/.config/func/boson/go/http"
|
|
// Specified when writing templates as simply:
|
|
// Write([runtime], [repository], [path])
|
|
// For example
|
|
// w := templateWriter{templates:"/home/username/.config/func/templates")
|
|
// w.Write("go", "boson/http")
|
|
// Ie. "Using the custom templates in the func configuration directory,
|
|
// write the Boson HTTP template for the Go runtime."
|
|
templates string
|
|
verbose bool
|
|
}
|
|
|
|
var (
|
|
ErrRepositoryNotFound = errors.New("repository not found")
|
|
ErrRepositoriesNotDefined = errors.New("custom template repositories location not specified")
|
|
ErrRuntimeNotFound = errors.New("runtime not found")
|
|
ErrTemplateNotFound = errors.New("template not found")
|
|
ErrTemplateMissingRepository = errors.New("template name missing repository prefix")
|
|
)
|
|
|
|
func (t templateWriter) Write(runtime, template, dest string) error {
|
|
if template == "" {
|
|
template = DefaultTemplate
|
|
}
|
|
|
|
if isCustom(template) {
|
|
return writeCustom(t.templates, runtime, template, dest)
|
|
}
|
|
|
|
return writeEmbedded(runtime, template, dest)
|
|
}
|
|
|
|
func isCustom(template string) bool {
|
|
return len(strings.Split(template, "/")) > 1
|
|
}
|
|
|
|
func writeCustom(templatesPath, runtime, templateFullName, dest string) error {
|
|
if templatesPath == "" {
|
|
return ErrRepositoriesNotDefined
|
|
}
|
|
|
|
if !repositoryExists(templatesPath, templateFullName) {
|
|
return ErrRepositoryNotFound
|
|
}
|
|
|
|
// ensure that the templateFullName is of the format "repoName/templateName"
|
|
cc := strings.Split(templateFullName, "/")
|
|
if len(cc) != 2 {
|
|
return ErrTemplateMissingRepository
|
|
}
|
|
repo := cc[0]
|
|
template := cc[1]
|
|
|
|
runtimePath := filepath.Join(templatesPath, repo, runtime)
|
|
_, err := os.Stat(runtimePath)
|
|
if err != nil {
|
|
return ErrRuntimeNotFound
|
|
}
|
|
|
|
// Example FileSystem path:
|
|
// /home/alice/.config/func/templates/boson/go/json
|
|
templatePath := filepath.Join(templatesPath, repo, runtime, template)
|
|
_, err = os.Stat(templatePath)
|
|
if err != nil {
|
|
return ErrTemplateNotFound
|
|
}
|
|
return copy(templatePath, dest, filesystemAccessor{})
|
|
}
|
|
|
|
func writeEmbedded(runtime, template, dest string) (err error) {
|
|
// Copy files to the destination
|
|
// Example embedded path:
|
|
// /templates/go/http
|
|
runtimePath := filepath.Join("/templates", runtime)
|
|
_, err = pkger.Stat(runtimePath)
|
|
if err != nil {
|
|
return ErrRuntimeNotFound
|
|
}
|
|
|
|
templatePath := filepath.Join("/templates", runtime, template)
|
|
_, err = pkger.Stat(templatePath)
|
|
if err != nil {
|
|
return ErrTemplateNotFound
|
|
}
|
|
|
|
return copy(templatePath, dest, embeddedAccessor{})
|
|
}
|
|
|
|
type embeddedAccessor struct{}
|
|
|
|
func (a embeddedAccessor) Stat(path string) (os.FileInfo, error) {
|
|
return pkger.Stat(path)
|
|
}
|
|
|
|
func (a embeddedAccessor) Open(path string) (file, error) {
|
|
return pkger.Open(path)
|
|
}
|
|
|
|
type filesystemAccessor struct{}
|
|
|
|
func (a filesystemAccessor) Stat(path string) (os.FileInfo, error) {
|
|
return os.Stat(path)
|
|
}
|
|
|
|
func (a filesystemAccessor) Open(path string) (file, error) {
|
|
return os.Open(path)
|
|
}
|
|
|
|
func repositoryExists(repositories, template string) bool {
|
|
cc := strings.Split(template, "/")
|
|
_, err := os.Stat(filepath.Join(repositories, cc[0]))
|
|
return err == nil
|
|
}
|
|
|
|
func copy(src, dest string, accessor fileAccessor) (err error) {
|
|
node, err := accessor.Stat(src)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if node.IsDir() {
|
|
return copyNode(src, dest, accessor)
|
|
} else {
|
|
return copyLeaf(src, dest, accessor)
|
|
}
|
|
}
|
|
|
|
func copyNode(src, dest string, accessor fileAccessor) (err error) {
|
|
node, err := accessor.Stat(src)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = os.MkdirAll(dest, node.Mode())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
children, err := readDir(src, accessor)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, child := range children {
|
|
if err = copy(filepath.Join(src, child.Name()), filepath.Join(dest, child.Name()), accessor); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func readDir(src string, accessor fileAccessor) ([]os.FileInfo, error) {
|
|
f, err := accessor.Open(src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
list, err := f.Readdir(-1)
|
|
f.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
|
|
return list, nil
|
|
}
|
|
|
|
func copyLeaf(src, dest string, accessor fileAccessor) (err error) {
|
|
srcFile, err := accessor.Open(src)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer srcFile.Close()
|
|
|
|
srcFileInfo, err := accessor.Stat(src)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
destFile, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcFileInfo.Mode())
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer destFile.Close()
|
|
|
|
_, err = io.Copy(destFile, srcFile)
|
|
return
|
|
}
|