mirror of https://github.com/knative/func.git
171 lines
4.8 KiB
Go
171 lines
4.8 KiB
Go
package function
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
ErrRepositoryNotFound = errors.New("repository not found")
|
|
ErrRepositoriesNotDefined = errors.New("custom template repositories location not specified")
|
|
ErrTemplatesNotFound = errors.New("templates path (runtimes) not found")
|
|
ErrRuntimeNotFound = errors.New("runtime not found")
|
|
ErrTemplateNotFound = errors.New("template not found")
|
|
ErrTemplateMissingRepository = errors.New("template name missing repository prefix")
|
|
)
|
|
|
|
// Templates Manager
|
|
type Templates struct {
|
|
client *Client
|
|
}
|
|
|
|
// newTemplates manager
|
|
// Includes a back-reference to client (logic tree root) such
|
|
// that the templates manager has full access to the API for
|
|
// use in its implementations.
|
|
func newTemplates(client *Client) *Templates {
|
|
return &Templates{client: client}
|
|
}
|
|
|
|
// List the full name of templates available for the runtime.
|
|
// Full name is the optional repository prefix plus the template's repository
|
|
// local name. Default templates grouped first sans prefix.
|
|
func (t *Templates) List(runtime string) ([]string, error) {
|
|
names := []string{}
|
|
extended := newSortedSet()
|
|
|
|
rr, err := t.client.Repositories().All()
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
|
|
for _, r := range rr {
|
|
tt, err := r.Templates(runtime)
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
for _, t := range tt {
|
|
if r.Name == DefaultRepositoryName {
|
|
names = append(names, t.Name)
|
|
} else {
|
|
extended.Add(t.Fullname())
|
|
}
|
|
}
|
|
}
|
|
return append(names, extended.Items()...), nil
|
|
}
|
|
|
|
// Template returns the named template in full form '[repo]/[name]' for the
|
|
// specified runtime.
|
|
// Templates from the default repository do not require the repo name prefix,
|
|
// though it can be provided.
|
|
func (t *Templates) Get(runtime, fullname string) (Template, error) {
|
|
var (
|
|
template Template
|
|
repoName string
|
|
tplName string
|
|
repo Repository
|
|
err error
|
|
)
|
|
|
|
// Split into repo and template names.
|
|
// Defaults when unprefixed to DefaultRepository
|
|
cc := strings.Split(fullname, "/")
|
|
if len(cc) == 1 {
|
|
repoName = DefaultRepositoryName
|
|
tplName = fullname
|
|
} else {
|
|
repoName = cc[0]
|
|
tplName = cc[1]
|
|
}
|
|
|
|
// Get specified repository
|
|
repo, err = t.client.Repositories().Get(repoName)
|
|
if err != nil {
|
|
return template, err
|
|
}
|
|
|
|
return repo.Template(runtime, tplName)
|
|
}
|
|
|
|
// Write a function's template to disk.
|
|
// Returns a Function which may have been modified dependent on the content
|
|
// of the template (which can define default Function fields, builders,
|
|
// buildpacks, etc)
|
|
func (t *Templates) Write(f Function) (Function, error) {
|
|
// Templates require an initially valid Function to write
|
|
// (has name, path, runtime etc)
|
|
if err := f.Validate(); err != nil {
|
|
return f, err
|
|
}
|
|
|
|
// The Function's Template
|
|
template, err := t.Get(f.Runtime, f.Template)
|
|
if err != nil {
|
|
return f, err
|
|
}
|
|
|
|
// The Function's Template Repository
|
|
repo, err := t.client.Repositories().Get(template.Repository)
|
|
if err != nil {
|
|
return f, err
|
|
}
|
|
|
|
// Validate paths: (repo/)[templates/]<runtime>/<template>
|
|
templatesPath := repo.TemplatesPath
|
|
if templatesPath == "" {
|
|
templatesPath = "."
|
|
}
|
|
if _, err := repo.FS.Stat(templatesPath); err != nil {
|
|
return f, ErrTemplatesNotFound
|
|
}
|
|
runtimePath := path.Join(templatesPath, template.Runtime)
|
|
if _, err := repo.FS.Stat(runtimePath); err != nil {
|
|
return f, ErrRuntimeNotFound
|
|
}
|
|
templatePath := path.Join(runtimePath, template.Name)
|
|
if _, err := repo.FS.Stat(templatePath); err != nil {
|
|
return f, ErrTemplateNotFound
|
|
}
|
|
|
|
// Apply fields from the template onto the function itself (Denormalize).
|
|
// The template is already the denormalized view of repo->runtime->template
|
|
// so it's values are treated as defaults.
|
|
// TODO: this begs the question: should the Template's manifest.yaml actually
|
|
// be a partially-populated func.yaml?
|
|
if f.Builder == "" { // as a special first case, this default comes from itself
|
|
f.Builder = f.Builders["default"]
|
|
if f.Builder == "" { // still nothing? then use the template
|
|
f.Builder = template.Builders["default"]
|
|
}
|
|
}
|
|
if len(f.Builders) == 0 {
|
|
f.Builders = template.Builders
|
|
}
|
|
if len(f.Buildpacks) == 0 {
|
|
f.Buildpacks = template.Buildpacks
|
|
}
|
|
if len(f.BuildEnvs) == 0 {
|
|
f.BuildEnvs = template.BuildEnvs
|
|
}
|
|
if f.HealthEndpoints.Liveness == "" {
|
|
f.HealthEndpoints.Liveness = template.HealthEndpoints.Liveness
|
|
}
|
|
if f.HealthEndpoints.Readiness == "" {
|
|
f.HealthEndpoints.Readiness = template.HealthEndpoints.Readiness
|
|
}
|
|
if f.Invocation.Format == "" {
|
|
f.Invocation.Format = template.Invocation.Format
|
|
}
|
|
|
|
// Copy the template files from the repo filesystem to the new Function's root
|
|
// removing the manifest (if it exists; errors ignored)
|
|
err = copy(templatePath, f.Root, repo.FS) // copy everything
|
|
_ = os.Remove(filepath.Join(f.Root, templateManifest)) // except the manifest
|
|
|
|
return f, err
|
|
}
|