mirror of https://github.com/knative/func.git
feat: repository and templates client api (#475)
* feat: repositories accessor * feat: repository and templates client api - Templates management api - Repositories management api expansion * fix: nil pointer reference on generate * src: remove unused test functions * src: test temp directory name consistency and comment improvements
This commit is contained in:
parent
a4b15ad992
commit
3f56a8fd7a
2
Makefile
2
Makefile
|
@ -53,7 +53,7 @@ $(BIN): $(CODE)
|
|||
env CGO_ENABLED=0 go build -ldflags $(LDFLAGS) ./cmd/$(BIN)
|
||||
|
||||
test: $(CODE) ## Run core unit tests
|
||||
go test -race -cover -coverprofile=coverage.out -v ./...
|
||||
go test -race -cover -coverprofile=coverage.out ./...
|
||||
|
||||
check: bin/golangci-lint ## Check code quality (lint)
|
||||
./bin/golangci-lint run --timeout 300s
|
||||
|
|
81
client.go
81
client.go
|
@ -8,23 +8,35 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultRegistry through which containers of Functions will be shuttled.
|
||||
DefaultRegistry = "docker.io"
|
||||
DefaultRuntime = "node"
|
||||
|
||||
// DefaultRuntime is the runtime language for a new Function, including
|
||||
// the template written and builder invoked on deploy.
|
||||
DefaultRuntime = "node"
|
||||
|
||||
// DefautlTemplate is the default Function signature / environmental context
|
||||
// of the resultant function. All runtimes are expected to have at least
|
||||
// one implementation of each supported funciton sinagure. Currently that
|
||||
// includes an HTTP Handler ("http") and Cloud Events handler ("events")
|
||||
DefaultTemplate = "http"
|
||||
|
||||
// DefaultRepository is the name of the default (builtin) template repository,
|
||||
// and is assumed when no template prefix is provided.
|
||||
DefaultRepository = "default"
|
||||
)
|
||||
|
||||
// Client for managing Function instances.
|
||||
type Client struct {
|
||||
Repositories *Repositories // Repository management
|
||||
Repositories *Repositories // Repositories management
|
||||
Templates *Templates // Templates management
|
||||
|
||||
verbose bool // print verbose logs
|
||||
builder Builder // Builds a runnable image from Function source
|
||||
|
@ -166,6 +178,7 @@ func New(options ...Option) *Client {
|
|||
// Instantiate client with static defaults.
|
||||
c := &Client{
|
||||
Repositories: &Repositories{},
|
||||
Templates: &Templates{},
|
||||
builder: &noopBuilder{output: os.Stdout},
|
||||
pusher: &noopPusher{output: os.Stdout},
|
||||
deployer: &noopDeployer{output: os.Stdout},
|
||||
|
@ -177,7 +190,13 @@ func New(options ...Option) *Client {
|
|||
emitter: &noopEmitter{},
|
||||
}
|
||||
|
||||
// Apply passed options, which take ultimate precidence.
|
||||
// TODO: Repositories default location ($XDG_CONFIG_HOME/func/repositories)
|
||||
// will be relocated from CLI to here.
|
||||
// c.Repositories.Path = ...
|
||||
|
||||
// Templates management requires the repositories management api
|
||||
c.Templates.Repositories = c.Repositories
|
||||
|
||||
for _, o := range options {
|
||||
o(c)
|
||||
}
|
||||
|
@ -618,6 +637,45 @@ func (c *Client) Emit(ctx context.Context, endpoint string) error {
|
|||
return c.emitter.Emit(ctx, endpoint)
|
||||
}
|
||||
|
||||
// sorted set of strings.
|
||||
//
|
||||
// write-optimized and suitable only for fairly small values of N.
|
||||
// Should this increase dramatically in size, a different implementation,
|
||||
// such as a linked list, might be more appropriate.
|
||||
type sortedSet struct {
|
||||
members map[string]bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func newSortedSet() *sortedSet {
|
||||
return &sortedSet{
|
||||
members: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sortedSet) Add(value string) {
|
||||
s.Lock()
|
||||
s.members[value] = true
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *sortedSet) Remove(value string) {
|
||||
s.Lock()
|
||||
delete(s.members, value)
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *sortedSet) Items() []string {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
n := []string{}
|
||||
for k := range s.members {
|
||||
n = append(n, k)
|
||||
}
|
||||
sort.Strings(n)
|
||||
return n
|
||||
}
|
||||
|
||||
// Manual implementations (noops) of required interfaces.
|
||||
// In practice, the user of this client package (for example the CLI) will
|
||||
// provide a concrete implementation for only the interfaces necessary to
|
||||
|
@ -630,36 +688,49 @@ func (c *Client) Emit(ctx context.Context, endpoint string) error {
|
|||
// with a minimum of external dependencies.
|
||||
// -----------------------------------------------------
|
||||
|
||||
// Builder
|
||||
type noopBuilder struct{ output io.Writer }
|
||||
|
||||
func (n *noopBuilder) Build(ctx context.Context, _ Function) error { return nil }
|
||||
|
||||
// Pusher
|
||||
type noopPusher struct{ output io.Writer }
|
||||
|
||||
func (n *noopPusher) Push(ctx context.Context, f Function) (string, error) { return "", nil }
|
||||
|
||||
// Deployer
|
||||
type noopDeployer struct{ output io.Writer }
|
||||
|
||||
func (n *noopDeployer) Deploy(ctx context.Context, _ Function) (DeploymentResult, error) {
|
||||
return DeploymentResult{}, nil
|
||||
}
|
||||
|
||||
// Runner
|
||||
type noopRunner struct{ output io.Writer }
|
||||
|
||||
func (n *noopRunner) Run(_ context.Context, _ Function) error { return nil }
|
||||
|
||||
// Remover
|
||||
type noopRemover struct{ output io.Writer }
|
||||
|
||||
func (n *noopRemover) Remove(context.Context, string) error { return nil }
|
||||
|
||||
// Lister
|
||||
type noopLister struct{ output io.Writer }
|
||||
|
||||
func (n *noopLister) List(context.Context) ([]ListItem, error) { return []ListItem{}, nil }
|
||||
|
||||
// Emitter
|
||||
type noopEmitter struct{}
|
||||
|
||||
func (p *noopEmitter) Emit(ctx context.Context, endpoint string) error { return nil }
|
||||
|
||||
// DNSProvider
|
||||
type noopDNSProvider struct{ output io.Writer }
|
||||
|
||||
func (n *noopDNSProvider) Provide(_ Function) error { return nil }
|
||||
|
||||
// ProgressListener
|
||||
type NoopProgressListener struct{}
|
||||
|
||||
func (p *NoopProgressListener) SetTotal(i int) {}
|
||||
|
@ -667,7 +738,3 @@ func (p *NoopProgressListener) Increment(m string) {}
|
|||
func (p *NoopProgressListener) Complete(m string) {}
|
||||
func (p *NoopProgressListener) Stopping() {}
|
||||
func (p *NoopProgressListener) Done() {}
|
||||
|
||||
type noopEmitter struct{}
|
||||
|
||||
func (p *noopEmitter) Emit(ctx context.Context, endpoint string) error { return nil }
|
||||
|
|
136
client_test.go
136
client_test.go
|
@ -19,7 +19,7 @@ const (
|
|||
// TestRegistry for calculating destination image during tests.
|
||||
// Will be optional once we support in-cluster container registries
|
||||
// by default. See TestRegistryRequired for details.
|
||||
TestRegistry = "quay.io/alice"
|
||||
TestRegistry = "example.com/alice"
|
||||
|
||||
// TestRuntime consists of a specially designed templates directory
|
||||
// used exclusively for tests.
|
||||
|
@ -31,65 +31,73 @@ const (
|
|||
// thus implicitly tests Create, Build and Deploy, which are exposed
|
||||
// by the client API for those who prefer manual transmissions.
|
||||
func TestNew(t *testing.T) {
|
||||
root := "testdata/example.com/testCreate" // Root from which to run the test
|
||||
root := "testdata/example.com/testNew"
|
||||
defer using(t, root)()
|
||||
|
||||
// New Client
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// New Function using Client
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTemplateWrites ensures a template is written.
|
||||
func TestTemplateWrites(t *testing.T) {
|
||||
root := "testdata/example.com/testCreateWrites"
|
||||
// TestWritesTemplate ensures the config file and files from the template
|
||||
// are written on new.
|
||||
func TestWritesTemplate(t *testing.T) {
|
||||
root := "testdata/example.com/testWritesTemplate"
|
||||
defer using(t, root)()
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
if err := client.Create(fn.Function{Root: root}); err != nil {
|
||||
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Assert file was written
|
||||
// Assert the standard config file was written
|
||||
if _, err := os.Stat(filepath.Join(root, fn.ConfigFile)); os.IsNotExist(err) {
|
||||
t.Fatalf("Initialize did not result in '%v' being written to '%v'", fn.ConfigFile, root)
|
||||
}
|
||||
|
||||
// Assert a file from the template was written
|
||||
if _, err := os.Stat(filepath.Join(root, "README.md")); os.IsNotExist(err) {
|
||||
t.Fatalf("Initialize did not result in '%v' being written to '%v'", fn.ConfigFile, root)
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtantAborts ensures that a directory which contains an extant
|
||||
// Function does not reinitialize
|
||||
// Function does not reinitialize.
|
||||
func TestExtantAborts(t *testing.T) {
|
||||
root := "testdata/example.com/testCreateInitializedAborts"
|
||||
root := "testdata/example.com/testExtantAborts"
|
||||
defer using(t, root)()
|
||||
|
||||
// New once
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// First .New should succeed...
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// New again should fail as already initialized
|
||||
// Calling again should abort...
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err == nil {
|
||||
t.Fatal("error expected initilizing a path already containing an initialized Function")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNonemptyDirectoryAborts ensures that a directory which contains any
|
||||
// visible files aborts.
|
||||
func TestNonemptyDirectoryAborts(t *testing.T) {
|
||||
root := "testdata/example.com/testCreateNonemptyDirectoryAborts"
|
||||
// TestNonemptyAborts ensures that a directory which contains any
|
||||
// (visible) files aborts.
|
||||
func TestNonemptyAborts(t *testing.T) {
|
||||
root := "testdata/example.com/testNonemptyAborts"
|
||||
defer using(t, root)()
|
||||
|
||||
// An unexpected, non-hidden file.
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// Write a visible file which should cause an aboert
|
||||
visibleFile := filepath.Join(root, "file.txt")
|
||||
if err := ioutil.WriteFile(visibleFile, []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
// Ceation should abort due to the visible file
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err == nil {
|
||||
t.Fatal("error expected initilizing a Function in a nonempty directory")
|
||||
}
|
||||
|
@ -102,16 +110,18 @@ func TestNonemptyDirectoryAborts(t *testing.T) {
|
|||
// conjunction with other tools (.envrc, etc)
|
||||
func TestHiddenFilesIgnored(t *testing.T) {
|
||||
// Create a directory for the Function
|
||||
root := "testdata/example.com/testCreateHiddenFilesIgnored"
|
||||
root := "testdata/example.com/testHiddenFilesIgnored"
|
||||
defer using(t, root)()
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// Create a hidden file that should be ignored.
|
||||
hiddenFile := filepath.Join(root, ".envrc")
|
||||
if err := ioutil.WriteFile(hiddenFile, []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
// Should succeed without error, ignoring the hidden file.
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -121,11 +131,12 @@ func TestHiddenFilesIgnored(t *testing.T) {
|
|||
// Functions and persisted.
|
||||
func TestDefaultRuntime(t *testing.T) {
|
||||
// Create a root for the new Function
|
||||
root := "testdata/example.com/testCreateDefaultRuntime"
|
||||
root := "testdata/example.com/testDefaultRuntime"
|
||||
defer using(t, root)()
|
||||
|
||||
// Create a new function at root with all defaults.
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// Create a new function at root with all defaults.
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -142,7 +153,7 @@ func TestDefaultRuntime(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestExtensibleRepositories ensures that templates are extensible
|
||||
// TestRepositoriesExtensible ensures that templates are extensible
|
||||
// using a custom path to template repositories on disk. The custom repositories
|
||||
// location is not defined herein but expected to be provided because, for
|
||||
// example, a CLI may want to use XDG_CONFIG_HOME. Assuming a repository path
|
||||
|
@ -151,12 +162,10 @@ func TestDefaultRuntime(t *testing.T) {
|
|||
// $FUNC_REPOSITORIES/boson/go/json
|
||||
// See the CLI for full details, but a standard default location is
|
||||
// $HOME/.config/func/repositories/boson/go/json
|
||||
func TestExtensibleRepositories(t *testing.T) {
|
||||
// Create a directory for the new Function
|
||||
root := "testdata/example.com/testExtensibleRepositories"
|
||||
func TestRepositoriesExtensible(t *testing.T) {
|
||||
root := "testdata/example.com/testRepositoriesExtensible"
|
||||
defer using(t, root)()
|
||||
|
||||
// Create a new client with a path to the extensible templates
|
||||
client := fn.New(
|
||||
fn.WithRepositories("testdata/repositories"),
|
||||
fn.WithRegistry(TestRegistry))
|
||||
|
@ -176,7 +185,6 @@ func TestExtensibleRepositories(t *testing.T) {
|
|||
|
||||
// TestRuntimeNotFound generates an error (embedded default repository).
|
||||
func TestRuntimeNotFound(t *testing.T) {
|
||||
// Create a directory for the Function
|
||||
root := "testdata/example.com/testRuntimeNotFound"
|
||||
defer using(t, root)()
|
||||
|
||||
|
@ -184,8 +192,7 @@ func TestRuntimeNotFound(t *testing.T) {
|
|||
|
||||
// creating a Function with an unsupported runtime should bubble
|
||||
// the error generated by the underlying template initializer.
|
||||
f := fn.Function{Root: root, Runtime: "invalid"}
|
||||
err := client.New(context.Background(), f)
|
||||
err := client.New(context.Background(), fn.Function{Root: root, Runtime: "invalid"})
|
||||
if !errors.Is(err, fn.ErrRuntimeNotFound) {
|
||||
t.Fatalf("Expected ErrRuntimeNotFound, got %T", err)
|
||||
}
|
||||
|
@ -258,7 +265,7 @@ func TestNamed(t *testing.T) {
|
|||
|
||||
// Path which would derive to testWithHame.example.com were it not for the
|
||||
// explicitly provided name.
|
||||
root := "testdata/example.com/testWithName"
|
||||
root := "testdata/example.com/testNamed"
|
||||
defer using(t, root)()
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
@ -289,7 +296,7 @@ func TestNamed(t *testing.T) {
|
|||
// this configuration parameter will become optional.
|
||||
func TestRegistryRequired(t *testing.T) {
|
||||
// Create a root for the Function
|
||||
root := "testdata/example.com/testRegistry"
|
||||
root := "testdata/example.com/testRegistryRequired"
|
||||
defer using(t, root)()
|
||||
|
||||
client := fn.New()
|
||||
|
@ -361,9 +368,9 @@ func TestDeriveImageDefaultRegistry(t *testing.T) {
|
|||
// Deploy (and confirms expected fields calculated).
|
||||
func TestNewDelegates(t *testing.T) {
|
||||
var (
|
||||
root = "testdata/example.com/testCreateDelegates" // .. in which to initialize
|
||||
expectedName = "testCreateDelegates" // expected to be derived
|
||||
expectedImage = "quay.io/alice/testCreateDelegates:latest"
|
||||
root = "testdata/example.com/testNewDelegates" // .. in which to initialize
|
||||
expectedName = "testNewDelegates" // expected to be derived
|
||||
expectedImage = "example.com/alice/testNewDelegates:latest"
|
||||
builder = mock.NewBuilder()
|
||||
pusher = mock.NewPusher()
|
||||
deployer = mock.NewDeployer()
|
||||
|
@ -471,7 +478,7 @@ func TestUpdate(t *testing.T) {
|
|||
var (
|
||||
root = "testdata/example.com/testUpdate"
|
||||
expectedName = "testUpdate"
|
||||
expectedImage = "quay.io/alice/testUpdate:latest"
|
||||
expectedImage = "example.com/alice/testUpdate:latest"
|
||||
builder = mock.NewBuilder()
|
||||
pusher = mock.NewPusher()
|
||||
deployer = mock.NewDeployer()
|
||||
|
@ -581,7 +588,7 @@ func TestRemoveByPath(t *testing.T) {
|
|||
// of the name provided, with precidence over a provided root path.
|
||||
func TestRemoveByName(t *testing.T) {
|
||||
var (
|
||||
root = "testdata/example.com/testRemoveByPath"
|
||||
root = "testdata/example.com/testRemoveByName"
|
||||
expectedName = "explicitName.example.com"
|
||||
remover = mock.NewRemover()
|
||||
)
|
||||
|
@ -683,7 +690,7 @@ func TestListOutsideRoot(t *testing.T) {
|
|||
// fully created (ie. was only initialized, not actually built and deploys)
|
||||
// yields an expected, and informative, error.
|
||||
func TestDeployUnbuilt(t *testing.T) {
|
||||
root := "testdata/example.com/testDeploy" // Root from which to run the test
|
||||
root := "testdata/example.com/testDeployUnbuilt" // Root from which to run the test
|
||||
defer using(t, root)()
|
||||
|
||||
// New Client
|
||||
|
@ -731,8 +738,7 @@ func TestEmit(t *testing.T) {
|
|||
|
||||
// Helpers ----
|
||||
|
||||
// using the given directory (creating it) returns a closure which removes the
|
||||
// directory, intended to be run in a defer statement.
|
||||
// USING: Make specified dir. Return deferrable cleanup fn.
|
||||
func using(t *testing.T, root string) func() {
|
||||
t.Helper()
|
||||
mkdir(t, root)
|
||||
|
@ -754,3 +760,51 @@ func rm(t *testing.T, dir string) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// MKTEMP: Create and CD to a temp dir.
|
||||
// Returns a deferrable cleanup fn.
|
||||
func mktemp(t *testing.T) (string, func()) {
|
||||
t.Helper()
|
||||
tmp := tempdir(t)
|
||||
owd := pwd(t)
|
||||
cd(t, tmp)
|
||||
return tmp, func() {
|
||||
os.RemoveAll(tmp)
|
||||
cd(t, owd)
|
||||
}
|
||||
}
|
||||
|
||||
func tempdir(t *testing.T) string {
|
||||
d, err := ioutil.TempDir("", "dir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func pwd(t *testing.T) string {
|
||||
d, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func cd(t *testing.T, dir string) {
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TEST REPO URI: Return URI to repo in ./testdata of matching name.
|
||||
// Suitable as URI for repository override. returns in form file://
|
||||
// Must be called prior to mktemp in tests which changes current
|
||||
// working directory as it depends on a relative path.
|
||||
// Repo uri: file://$(pwd)/testdata/repository.git (unix-like)
|
||||
// file: //$(pwd)\testdata\repository.git (windows)
|
||||
func testRepoURI(name string, t *testing.T) string {
|
||||
t.Helper()
|
||||
cwd, _ := os.Getwd()
|
||||
repo := filepath.Join(cwd, "testdata", name+".git")
|
||||
return fmt.Sprintf(`file://%s`, repo)
|
||||
}
|
||||
|
|
|
@ -10,10 +10,69 @@ import (
|
|||
"github.com/go-git/go-git/v5"
|
||||
)
|
||||
|
||||
// Repositories management.
|
||||
// Repositories manager
|
||||
type Repositories struct {
|
||||
// Path to repositories
|
||||
Path string
|
||||
Path string // Path to repositories
|
||||
}
|
||||
|
||||
// List all repositories installed at the defined root path plus builtin.
|
||||
func (r *Repositories) List() ([]string, error) {
|
||||
repositories, err := r.All()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
for _, repo := range repositories {
|
||||
names = append(names, repo.Name)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// All repositories under management (at configured Path)
|
||||
func (r *Repositories) All() (repos []Repository, err error) {
|
||||
repos = []Repository{}
|
||||
|
||||
// Single repo override
|
||||
// TODO: Create single remote repository override for WithRepository option.
|
||||
|
||||
// Default (builtin) repo always first
|
||||
builtin, err := NewRepositoryFromBuiltin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
repos = append(repos, builtin)
|
||||
|
||||
// read repos from filesystem (sorted by name)
|
||||
// TODO: when manifests are introduced, the final name may be different
|
||||
// than the name on the filesystem, and as such we can not rely on the
|
||||
// alphanumeric ordering of underlying list, and will instead have to sort
|
||||
// by configured name.
|
||||
ff, err := ioutil.ReadDir(r.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range ff {
|
||||
if !f.IsDir() || strings.HasPrefix(f.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
var repo Repository
|
||||
repo, err = NewRepositoryFromPath(filepath.Join(r.Path, f.Name()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// Get a repository by name, error if it does not exist.
|
||||
func (r *Repositories) Get(name string) (repo Repository, err error) {
|
||||
if name == DefaultRepository {
|
||||
return NewRepositoryFromBuiltin()
|
||||
}
|
||||
// TODO: when WithRepository defined, only it can be defined
|
||||
return NewRepositoryFromPath(filepath.Join(r.Path, name))
|
||||
}
|
||||
|
||||
// Add a repository of the given name from the URI. Name, if not provided,
|
||||
|
@ -45,21 +104,6 @@ func (r *Repositories) Remove(name string) error {
|
|||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// List repositories installed at the defined root path.
|
||||
func (r *Repositories) List() (list []string, err error) {
|
||||
list = []string{}
|
||||
ff, err := ioutil.ReadDir(r.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range ff {
|
||||
if f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
|
||||
list = append(list, f.Name())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// repoNameFrom uri returns the last token with any .git suffix trimmed.
|
||||
// uri must be parseable as a net/URL
|
||||
func repoNameFrom(uri string) (name string, err error) {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
package function_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -13,51 +12,110 @@ import (
|
|||
|
||||
const RepositoriesTestRepo = "repository-a"
|
||||
|
||||
// TestRepositoriesList ensures the base case of an empty list
|
||||
// when no repositories are installed.
|
||||
// TestRepositoriesList ensures the base case of listing
|
||||
// repositories without error in the default scenario of builtin only.
|
||||
func TestRepositoriesList(t *testing.T) {
|
||||
root, rm := mktemp(t)
|
||||
defer rm()
|
||||
|
||||
client := fn.New(fn.WithRepositories(root))
|
||||
client := fn.New(fn.WithRepositories(root)) // Explicitly empty
|
||||
|
||||
rr, err := client.Repositories.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr) != 0 {
|
||||
t.Fatalf("Expected an empty repositories list, got %v", len(rr))
|
||||
// Assert contains only the default repo
|
||||
if len(rr) != 1 && rr[0] != fn.DefaultRepository {
|
||||
t.Fatalf("Expected repository list '[%v]', got %v", fn.DefaultRepository, rr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRepositoriesGet ensures a repository can be accessed by name.
|
||||
func TestRepositoriesGet(t *testing.T) {
|
||||
client := fn.New(fn.WithRepositories("testdata/repositories"))
|
||||
|
||||
// invalid should error
|
||||
repo, err := client.Repositories.Get("invalid")
|
||||
if err == nil {
|
||||
t.Fatal("did not receive expected error getting inavlid repository")
|
||||
}
|
||||
|
||||
// valid should not error
|
||||
repo, err = client.Repositories.Get("customProvider")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// valid should have expected name
|
||||
if repo.Name != "customProvider" {
|
||||
t.Fatalf("expected 'customProvider' as repository name, got: %v", repo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRepositoriesAll ensures builtin and extended repos are returned from
|
||||
// .All accessor.
|
||||
func TestRepositoriesAll(t *testing.T) {
|
||||
uri := testRepoURI(RepositoriesTestRepo, t)
|
||||
root, rm := mktemp(t)
|
||||
defer rm()
|
||||
|
||||
client := fn.New(fn.WithRepositories(root))
|
||||
|
||||
// Assert initially only the default is included
|
||||
rr, err := client.Repositories.All()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr) != 1 && rr[0].Name != fn.DefaultRepository {
|
||||
t.Fatalf("Expected initial repo list to be only the default. Got %v", rr)
|
||||
}
|
||||
|
||||
// Add one
|
||||
err = client.Repositories.Add("", uri)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get full list
|
||||
repositories, err := client.Repositories.All()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Assert it now includes both builtin and extended
|
||||
if len(repositories) != 2 ||
|
||||
repositories[0].Name != fn.DefaultRepository ||
|
||||
repositories[1].Name != RepositoriesTestRepo {
|
||||
t.Fatal("Repositories list does not pass shallow repository membership check")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRepositoriesAdd ensures that adding a repository adds it to the FS
|
||||
// and List output. Uses default name (repo name).
|
||||
func TestRepositoriesAdd(t *testing.T) {
|
||||
uri := testRepoURI(t) // ./testdata/$RepositoriesTestRepo.git
|
||||
root, rm := mktemp(t) // create and cd to a temp dir
|
||||
uri := testRepoURI(RepositoriesTestRepo, t) // ./testdata/$RepositoriesTestRepo.git
|
||||
root, rm := mktemp(t) // create and cd to a temp dir
|
||||
defer rm()
|
||||
|
||||
// Instantiate the client using the current temp directory as the
|
||||
// repositories' root location.
|
||||
client := fn.New(fn.WithRepositories(root))
|
||||
|
||||
// Create a new repository without a name (use default of repo name)
|
||||
// Add repo at uri
|
||||
if err := client.Repositories.Add("", uri); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// assert list len 1
|
||||
// Assert list now includes the test repo
|
||||
rr, err := client.Repositories.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr) != 1 || rr[0] != RepositoriesTestRepo {
|
||||
if len(rr) != 2 || rr[1] != RepositoriesTestRepo {
|
||||
t.Fatalf("Expected '%v', got %v", RepositoriesTestRepo, rr)
|
||||
}
|
||||
|
||||
// assert expected name
|
||||
if rr[0] != RepositoriesTestRepo {
|
||||
t.Fatalf("Expected name '%v', got %v", RepositoriesTestRepo, rr[0])
|
||||
if rr[1] != RepositoriesTestRepo {
|
||||
t.Fatalf("Expected name '%v', got %v", RepositoriesTestRepo, rr[1])
|
||||
}
|
||||
|
||||
// assert repo was checked out
|
||||
|
@ -72,8 +130,8 @@ func TestRepositoriesAdd(t *testing.T) {
|
|||
// TestRepositoriesAddNamed ensures that adding a repository with a specified
|
||||
// name takes precidence over the default of repo name.
|
||||
func TestRepositoriesAddNamed(t *testing.T) {
|
||||
uri := testRepoURI(t) // ./testdata/$RepositoriesTestRepo.git
|
||||
root, rm := mktemp(t) // create and cd to a temp dir, returning path.
|
||||
uri := testRepoURI(RepositoriesTestRepo, t) // ./testdata/$RepositoriesTestRepo.git
|
||||
root, rm := mktemp(t) // create and cd to a temp dir, returning path.
|
||||
defer rm()
|
||||
|
||||
// Instantiate the client using the current temp directory as the
|
||||
|
@ -89,7 +147,7 @@ func TestRepositoriesAddNamed(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr) != 1 || rr[0] != name {
|
||||
if len(rr) != 2 || rr[1] != name {
|
||||
t.Fatalf("Expected '%v', got %v", name, rr)
|
||||
}
|
||||
|
||||
|
@ -102,7 +160,7 @@ func TestRepositoriesAddNamed(t *testing.T) {
|
|||
// TestRepositoriesAddExistingErrors ensures that adding a repository that
|
||||
// already exists yields an error.
|
||||
func TestRepositoriesAddExistingErrors(t *testing.T) {
|
||||
uri := testRepoURI(t)
|
||||
uri := testRepoURI(RepositoriesTestRepo, t)
|
||||
root, rm := mktemp(t) // create and cd to a temp dir, returning path.
|
||||
defer rm()
|
||||
|
||||
|
@ -124,7 +182,7 @@ func TestRepositoriesAddExistingErrors(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr) != 1 || rr[0] != name {
|
||||
if len(rr) != 2 || rr[1] != name {
|
||||
t.Fatalf("Expected '[%v]', got %v", name, rr)
|
||||
}
|
||||
|
||||
|
@ -136,7 +194,7 @@ func TestRepositoriesAddExistingErrors(t *testing.T) {
|
|||
|
||||
// TestRepositoriesRename ensures renaming a repository.
|
||||
func TestRepositoriesRename(t *testing.T) {
|
||||
uri := testRepoURI(t)
|
||||
uri := testRepoURI(RepositoriesTestRepo, t)
|
||||
root, rm := mktemp(t) // create and cd to a temp dir, returning path.
|
||||
defer rm()
|
||||
|
||||
|
@ -157,7 +215,7 @@ func TestRepositoriesRename(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr) != 1 || rr[0] != "bar" {
|
||||
if len(rr) != 2 || rr[1] != "bar" {
|
||||
t.Fatalf("Expected '[bar]', got %v", rr)
|
||||
}
|
||||
|
||||
|
@ -170,8 +228,8 @@ func TestRepositoriesRename(t *testing.T) {
|
|||
// TestRepositoriesRemove ensures that removing a repository by name
|
||||
// removes it from the list and FS.
|
||||
func TestRepositoriesRemove(t *testing.T) {
|
||||
uri := testRepoURI(t) // ./testdata/repository.git
|
||||
root, rm := mktemp(t) // create and cd to a temp dir, returning path.
|
||||
uri := testRepoURI(RepositoriesTestRepo, t) // ./testdata/repository.git
|
||||
root, rm := mktemp(t) // create and cd to a temp dir, returning path.
|
||||
defer rm()
|
||||
|
||||
// Instantiate the client using the current temp directory as the
|
||||
|
@ -192,8 +250,8 @@ func TestRepositoriesRemove(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr) != 0 {
|
||||
t.Fatalf("Expected empty repo list upon remove. Got %v", rr)
|
||||
if len(rr) != 1 {
|
||||
t.Fatalf("Expected repo list of len 1. Got %v", rr)
|
||||
}
|
||||
|
||||
// assert repo not on filesystem
|
||||
|
@ -201,52 +259,3 @@ func TestRepositoriesRemove(t *testing.T) {
|
|||
t.Fatalf("Repo %v still exists on filesystem.", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers ---
|
||||
|
||||
// testRepoURI returns a file:// URI to a test repository in
|
||||
// testdata. Must be called prior to mktemp in tests which changes current
|
||||
// working directory.
|
||||
// Repo uri: file://$(pwd)/testdata/repository.git (unix-like)
|
||||
// file: //$(pwd)\testdata\repository.git (windows)
|
||||
func testRepoURI(t *testing.T) string {
|
||||
t.Helper()
|
||||
cwd, _ := os.Getwd()
|
||||
repo := filepath.Join(cwd, "testdata", RepositoriesTestRepo+".git")
|
||||
return "file://" + filepath.ToSlash(repo)
|
||||
}
|
||||
|
||||
// mktemp creates a temp dir, returning its path
|
||||
// and a function which will remove it.
|
||||
func mktemp(t *testing.T) (string, func()) {
|
||||
t.Helper()
|
||||
tmp := mktmp(t)
|
||||
owd := pwd(t)
|
||||
cd(t, tmp)
|
||||
return tmp, func() {
|
||||
os.RemoveAll(tmp)
|
||||
cd(t, owd)
|
||||
}
|
||||
}
|
||||
|
||||
func mktmp(t *testing.T) string {
|
||||
d, err := ioutil.TempDir("", "dir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func pwd(t *testing.T) string {
|
||||
d, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func cd(t *testing.T, dir string) {
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package function
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/markbates/pkger"
|
||||
)
|
||||
|
||||
// Path to builtin repositories.
|
||||
// note: this constant must be defined in the same file in which it is used due
|
||||
// to pkger performing static analysis on source files separately.
|
||||
const builtinRepositories = "/templates"
|
||||
|
||||
// Repository
|
||||
type Repository struct {
|
||||
Name string
|
||||
Templates []Template
|
||||
Runtimes []string
|
||||
}
|
||||
|
||||
// NewRepository from path.
|
||||
// Represents the file structure of 'path' at time of construction as
|
||||
// a Repository with Templates, each of which has a Name and its Runtime.
|
||||
// a convenience member of Runtimes is the unique, sorted list of all
|
||||
// runtimes
|
||||
func NewRepositoryFromPath(path string) (Repository, error) {
|
||||
// TODO: read and use manifest if it exists
|
||||
|
||||
r := Repository{
|
||||
Name: filepath.Base(path),
|
||||
Templates: []Template{},
|
||||
Runtimes: []string{}}
|
||||
|
||||
// Each subdirectory is a Runtime
|
||||
runtimes, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
for _, runtime := range runtimes {
|
||||
if !runtime.IsDir() || strings.HasPrefix(runtime.Name(), ".") {
|
||||
continue // ignore files and hidden
|
||||
}
|
||||
r.Runtimes = append(r.Runtimes, runtime.Name())
|
||||
|
||||
// Each subdirectory is a Template
|
||||
templates, err := ioutil.ReadDir(filepath.Join(path, runtime.Name()))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
for _, template := range templates {
|
||||
if !template.IsDir() || strings.HasPrefix(template.Name(), ".") {
|
||||
continue // ignore files and hidden
|
||||
}
|
||||
r.Templates = append(r.Templates, Template{
|
||||
Runtime: runtime.Name(),
|
||||
Repository: r.Name,
|
||||
Name: template.Name()})
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// NewRepository from builtin (encoded ./templates)
|
||||
func NewRepositoryFromBuiltin() (Repository, error) {
|
||||
r := Repository{
|
||||
Name: DefaultRepository,
|
||||
Templates: []Template{},
|
||||
Runtimes: []string{}}
|
||||
|
||||
// Read in runtimes
|
||||
dir, err := pkger.Open(builtinRepositories)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
runtimes, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
for _, runtime := range runtimes {
|
||||
if !runtime.IsDir() || strings.HasPrefix(runtime.Name(), ".") {
|
||||
continue // ignore from runtimes non-directory or hidden items
|
||||
}
|
||||
r.Runtimes = append(r.Runtimes, runtime.Name())
|
||||
|
||||
// Each subdirectory is a Template
|
||||
templateDir, err := pkger.Open(filepath.Join(builtinRepositories, runtime.Name()))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
templates, err := templateDir.Readdir(-1)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
for _, template := range templates {
|
||||
if !template.IsDir() || strings.HasPrefix(template.Name(), ".") {
|
||||
continue // ignore from templates non-directory or hidden items
|
||||
}
|
||||
r.Templates = append(r.Templates, Template{
|
||||
Runtime: runtime.Name(),
|
||||
Repository: r.Name,
|
||||
Name: template.Name(),
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetTemplate from repo with given runtime
|
||||
func (r *Repository) GetTemplate(runtime, name string) (Template, error) {
|
||||
// TODO: return a typed RuntimeNotFound in repo X
|
||||
// rather than the generic Template Not Found
|
||||
for _, t := range r.Templates {
|
||||
if t.Runtime == runtime && t.Name == name {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
// TODO: Typed TemplateNotFound in repo X
|
||||
return Template{}, errors.New("template not found")
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package function_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
fn "knative.dev/kn-plugin-func"
|
||||
)
|
||||
|
||||
// TestRepositoryGetTemplateDefault ensures that repositories make templates
|
||||
// avaialble via the Get accessor which given name and runtime.
|
||||
func TestRepositoryGetTemplateDefault(t *testing.T) {
|
||||
client := fn.New()
|
||||
|
||||
repo, err := client.Repositories.Get(fn.DefaultRepository)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
template, err := repo.GetTemplate("go", "http")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := fn.Template{
|
||||
Runtime: "go",
|
||||
Repository: fn.DefaultRepository,
|
||||
Name: "http",
|
||||
}
|
||||
if !reflect.DeepEqual(template, expected) {
|
||||
t.Logf("expected: %v", expected)
|
||||
t.Logf("received: %v", template)
|
||||
t.Fatal("Default template not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRepositoryGetTemplateCustom ensures that repositories make templates
|
||||
// avaialble via the Get accessor with given name and runtime.
|
||||
func TestRepositoryGetTemplateCustom(t *testing.T) {
|
||||
client := fn.New(fn.WithRepositories("testdata/repositories"))
|
||||
|
||||
repo, err := client.Repositories.Get("repositoryTests")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
template, err := repo.GetTemplate("go", "custom")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := fn.Template{
|
||||
Runtime: "go",
|
||||
Repository: "repositoryTests",
|
||||
Name: "custom",
|
||||
}
|
||||
if !reflect.DeepEqual(template, expected) {
|
||||
t.Logf("expected: %v", expected)
|
||||
t.Logf("received: %v", template)
|
||||
t.Fatal("Custom template not as expected")
|
||||
}
|
||||
|
||||
}
|
126
templates.go
126
templates.go
|
@ -20,6 +20,128 @@ import (
|
|||
"github.com/markbates/pkger"
|
||||
)
|
||||
|
||||
// Path to builtin
|
||||
// note: this constant must be redefined in each file used due to pkger
|
||||
// performing static analysis on each source file separately.
|
||||
const builtinPath = "/templates"
|
||||
|
||||
// Templates Manager
|
||||
type Templates struct {
|
||||
Repositories *Repositories // Repository Manager
|
||||
}
|
||||
|
||||
// Template metadata
|
||||
type Template struct {
|
||||
Runtime string
|
||||
Repository string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Fullname is a caluclate field of [repo]/[name] used
|
||||
// to uniquely reference a template which may share a name
|
||||
// with one in another repository.
|
||||
func (t Template) Fullname() string {
|
||||
return t.Repository + "/" + t.Name
|
||||
}
|
||||
|
||||
// List the full name of templates available 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) {
|
||||
// TODO: if repository override was enabled, we should just return those, flat.
|
||||
builtin, err := t.ListDefault(runtime)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
extended, err := t.ListExtended(runtime)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
// Result is an alphanumerically sorted list first grouped by
|
||||
// embedded at head.
|
||||
return append(builtin, extended...), nil
|
||||
}
|
||||
|
||||
// ListDefault (embedded) templates by runtime
|
||||
func (t *Templates) ListDefault(runtime string) ([]string, error) {
|
||||
var (
|
||||
names = newSortedSet()
|
||||
repo, err = t.Repositories.Get(DefaultRepository)
|
||||
)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
for _, template := range repo.Templates {
|
||||
if template.Runtime != runtime {
|
||||
continue
|
||||
}
|
||||
names.Add(template.Name)
|
||||
}
|
||||
return names.Items(), nil
|
||||
}
|
||||
|
||||
// ListExtended templates returns all template full names that
|
||||
// exist in all extended (config dir) repositories for a runtime.
|
||||
// Prefixed, sorted.
|
||||
func (t *Templates) ListExtended(runtime string) ([]string, error) {
|
||||
var (
|
||||
names = newSortedSet()
|
||||
repos, err = t.Repositories.All()
|
||||
)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
for _, repo := range repos {
|
||||
if repo.Name == DefaultRepository {
|
||||
continue // already added at head of names
|
||||
}
|
||||
for _, template := range repo.Templates {
|
||||
if template.Runtime != runtime {
|
||||
continue
|
||||
}
|
||||
names.Add(template.Fullname())
|
||||
}
|
||||
}
|
||||
return names.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 = DefaultRepository
|
||||
tplName = fullname
|
||||
} else {
|
||||
repoName = cc[0]
|
||||
tplName = cc[1]
|
||||
}
|
||||
|
||||
// Get specified repository
|
||||
repo, err = t.Repositories.Get(repoName)
|
||||
if err != nil {
|
||||
return template, err
|
||||
}
|
||||
|
||||
return repo.GetTemplate(runtime, tplName)
|
||||
}
|
||||
|
||||
// Writing ------
|
||||
|
||||
type filesystem interface {
|
||||
Stat(name string) (os.FileInfo, error)
|
||||
Open(path string) (file, error)
|
||||
|
@ -38,7 +160,7 @@ type file interface {
|
|||
// into pkged.go, which is then made available via a pkger filesystem. Path is
|
||||
// relative to the go module root.
|
||||
func init() {
|
||||
_ = pkger.Include("/templates")
|
||||
_ = pkger.Include(builtinPath)
|
||||
}
|
||||
|
||||
type templateWriter struct {
|
||||
|
@ -292,7 +414,7 @@ func (a pkgerFilesystem) ReadDir(path string) ([]os.FileInfo, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.Readdir(-1) // Really? Pkger's ReadDir is Readdir.
|
||||
return f.Readdir(-1)
|
||||
}
|
||||
|
||||
// billyFilesystem is a template file accessor backed by a billy FS
|
||||
|
|
|
@ -7,13 +7,89 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
fn "knative.dev/kn-plugin-func"
|
||||
)
|
||||
|
||||
// TestTemplateEmbedded ensures that embedded templates are copied.
|
||||
// TestTemplatesList ensures that all templates are listed taking into account
|
||||
// both internal and extensible (prefixed) repositories.
|
||||
func TestTemplatesList(t *testing.T) {
|
||||
// A client which specifies a location of exensible repositoreis on disk
|
||||
// will list all builtin plus exensible
|
||||
client := fn.New(fn.WithRepositories("testdata/repositories"))
|
||||
|
||||
// list templates for the "go" runtime
|
||||
templates, err := client.Templates.List("go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Note that this list will change as the customProvider
|
||||
// and builtin templates are shared. THis could be mitigated
|
||||
// by creating a custom repository path for just this test, if
|
||||
// that becomes a hassle.
|
||||
expected := []string{
|
||||
"events",
|
||||
"http",
|
||||
"customProvider/customTemplate",
|
||||
"repositoryTests/custom",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(templates, expected) {
|
||||
t.Logf("expected: %v", expected)
|
||||
t.Logf("received: %v", templates)
|
||||
t.Fatal("Expected templates list not received.")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTemplatesGet ensures that a template's metadata object can
|
||||
// be retrieved by full name (full name prefix optional for embedded).
|
||||
func TestTemplatesGet(t *testing.T) {
|
||||
client := fn.New(fn.WithRepositories("testdata/repositories"))
|
||||
|
||||
// Check embedded
|
||||
|
||||
embedded, err := client.Templates.Get("go", "http")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := fn.Template{
|
||||
Runtime: "go",
|
||||
Repository: "default",
|
||||
Name: "http",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(embedded, expected) {
|
||||
t.Logf("expected: %v", expected)
|
||||
t.Logf("received: %v", embedded)
|
||||
t.Fatal("Template from embedded repo not as expected.")
|
||||
}
|
||||
|
||||
// Check extended
|
||||
|
||||
extended, err := client.Templates.Get("go", "customProvider/customTemplate")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected = fn.Template{
|
||||
Runtime: "go",
|
||||
Repository: "customProvider",
|
||||
Name: "customTemplate",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(extended, expected) {
|
||||
t.Logf("expected: %v", expected)
|
||||
t.Logf("received: %v", extended)
|
||||
t.Fatal("Template from extended repo not as expected.")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTemplateEmbedded ensures that embedded templates are copied on write.
|
||||
func TestTemplateEmbedded(t *testing.T) {
|
||||
// create test directory
|
||||
root := "testdata/testTemplateEmbedded"
|
||||
|
|
0
testdata/repositories/customProvider/customRuntime/customTemplate/custom.impl
vendored
Normal file
0
testdata/repositories/customProvider/customRuntime/customTemplate/custom.impl
vendored
Normal file
Loading…
Reference in New Issue