mirror of https://github.com/knative/func.git
feat: test suite
- updated tests to new api throughout - expanded tests where appropriate - lint issues - minor code review comments addressed
This commit is contained in:
parent
6e0f4caa93
commit
d33fb2d694
|
@ -7,3 +7,5 @@
|
||||||
|
|
||||||
target
|
target
|
||||||
node_modules
|
node_modules
|
||||||
|
/coverage.out
|
||||||
|
/bin
|
||||||
|
|
192
client.go
192
client.go
|
@ -18,8 +18,6 @@ const (
|
||||||
// Client for managing Function instances.
|
// Client for managing Function instances.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
verbose bool // print verbose logs
|
verbose bool // print verbose logs
|
||||||
local bool // Run in local-only mode
|
|
||||||
internal bool // Deploy without publicly accessible route
|
|
||||||
builder Builder // Builds a runnable image from Function source
|
builder Builder // Builds a runnable image from Function source
|
||||||
pusher Pusher // Pushes the image assocaited with a Function.
|
pusher Pusher // Pushes the image assocaited with a Function.
|
||||||
deployer Deployer // Deploys a Function
|
deployer Deployer // Deploys a Function
|
||||||
|
@ -151,20 +149,6 @@ func WithVerbose(v bool) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLocal sets the local mode
|
|
||||||
func WithLocal(l bool) Option {
|
|
||||||
return func(c *Client) {
|
|
||||||
c.local = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithInternal sets the internal (no public route) mode for deployed Function.
|
|
||||||
func WithInternal(i bool) Option {
|
|
||||||
return func(c *Client) {
|
|
||||||
c.internal = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithBuilder provides the concrete implementation of a builder.
|
// WithBuilder provides the concrete implementation of a builder.
|
||||||
func WithBuilder(d Builder) Option {
|
func WithBuilder(d Builder) Option {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
|
@ -266,6 +250,60 @@ func WithRepository(repository string) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a Function.
|
||||||
|
// Includes Initialization, Building, and Deploying.
|
||||||
|
func (c *Client) Create(cfg Function) (err error) {
|
||||||
|
c.progressListener.SetTotal(4)
|
||||||
|
defer c.progressListener.Done()
|
||||||
|
|
||||||
|
// Initialize, writing out a template implementation and a config file.
|
||||||
|
// TODO: the Function's Initialize parameters are slightly different than
|
||||||
|
// the Initializer interface, and can thus cause confusion (one passes an
|
||||||
|
// optional name the other passes root path). This could easily cause
|
||||||
|
// confusion and thus we may want to rename Initalizer to the more specific
|
||||||
|
// task it performs: ContextTemplateWriter or similar.
|
||||||
|
c.progressListener.Increment("Initializing new Function project")
|
||||||
|
err = c.Initialize(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the now-initialized Function.
|
||||||
|
f, err := NewFunction(cfg.Root)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the now-initialized Function
|
||||||
|
c.progressListener.Increment("Building container image")
|
||||||
|
if err = c.Build(f.Root); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy the initialized Function, returning its publicly
|
||||||
|
// addressible name for possible registration.
|
||||||
|
c.progressListener.Increment("Deploying Function to cluster")
|
||||||
|
if err = c.Deploy(f.Root); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an external route to the Function
|
||||||
|
c.progressListener.Increment("Creating route to Function")
|
||||||
|
if err = c.Route(f.Root); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.progressListener.Complete("Create complete")
|
||||||
|
|
||||||
|
// TODO: use the knative client during deployment such that the actual final
|
||||||
|
// route can be returned from the deployment step, passed to the DNS Router
|
||||||
|
// for routing actual traffic, and returned here.
|
||||||
|
if c.verbose {
|
||||||
|
fmt.Printf("https://%v/\n", f.Name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize creates a new Function project locally using the settings
|
// Initialize creates a new Function project locally using the settings
|
||||||
// provided on a Function object.
|
// provided on a Function object.
|
||||||
func (c *Client) Initialize(cfg Function) (err error) {
|
func (c *Client) Initialize(cfg Function) (err error) {
|
||||||
|
@ -275,17 +313,8 @@ func (c *Client) Initialize(cfg Function) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not initialize if already initialized.
|
// Assert the specified root is free of visible files and contentious
|
||||||
if f.Initialized() {
|
// hidden files (the ConfigFile, which indicates it is already initialized)
|
||||||
err = fmt.Errorf("Function at '%v' already initialized.", cfg.Root)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not re-initialize unless the directory is empty This is to protect the
|
|
||||||
// user from inadvertently overwriting local files which share the same name
|
|
||||||
// as those in the applied template if, for instance, an incorrect path is
|
|
||||||
// supplied for the new Function. This assertion is that the target path is
|
|
||||||
// empty of all but unrelated hidden files.
|
|
||||||
if err = assertEmptyRoot(f.Root); err != nil {
|
if err = assertEmptyRoot(f.Root); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -304,8 +333,6 @@ func (c *Client) Initialize(cfg Function) (err error) {
|
||||||
f.Runtime = cfg.Runtime
|
f.Runtime = cfg.Runtime
|
||||||
if f.Runtime == "" {
|
if f.Runtime == "" {
|
||||||
f.Runtime = DefaultRuntime
|
f.Runtime = DefaultRuntime
|
||||||
} else {
|
|
||||||
f.Runtime = cfg.Runtime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert trigger was provided, or default.
|
// Assert trigger was provided, or default.
|
||||||
|
@ -404,61 +431,6 @@ func (c *Client) Route(path string) (err error) {
|
||||||
return c.dnsProvider.Provide(f)
|
return c.dnsProvider.Provide(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a Function of the given runtime.
|
|
||||||
// Name and Root are optional:
|
|
||||||
// Name is derived from root if possible.
|
|
||||||
// Root is defaulted to the current working directory.
|
|
||||||
func (c *Client) Create(cfg Function) (err error) {
|
|
||||||
c.progressListener.SetTotal(4)
|
|
||||||
defer c.progressListener.Done()
|
|
||||||
|
|
||||||
// Initialize, writing out a template implementation and a config file.
|
|
||||||
// TODO: the Function's Initialize parameters are slightly different than
|
|
||||||
// the Initializer interface, and can thus cause confusion (one passes an
|
|
||||||
// optional name the other passes root path). This could easily cause
|
|
||||||
// confusion and thus we may want to rename Initalizer to the more specific
|
|
||||||
// task it performs: ContextTemplateWriter or similar.
|
|
||||||
c.progressListener.Increment("Initializing new Function project")
|
|
||||||
err = c.Initialize(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the now-initialized Function.
|
|
||||||
f, err := NewFunction(cfg.Root)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the now-initialized Function
|
|
||||||
c.progressListener.Increment("Building container image")
|
|
||||||
err = c.Build(f.Root)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deploy the initialized Function, returning its publicly
|
|
||||||
// addressible name for possible registration.
|
|
||||||
c.progressListener.Increment("Deploying Function to cluster")
|
|
||||||
if err = c.Deploy(f.Root); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an external route to the Function
|
|
||||||
c.progressListener.Increment("Creating route to Function")
|
|
||||||
if err = c.Route(f.Root); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.progressListener.Complete("Create complete")
|
|
||||||
|
|
||||||
// TODO: use the knative client during deployment such that the actual final
|
|
||||||
// route can be returned from the deployment step, passed to the DNS Router
|
|
||||||
// for routing actual traffic, and returned here.
|
|
||||||
fmt.Printf("https://%v/\n", f.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update a previously created Function.
|
// Update a previously created Function.
|
||||||
func (c *Client) Update(root string) (err error) {
|
func (c *Client) Update(root string) (err error) {
|
||||||
|
|
||||||
|
@ -539,20 +511,20 @@ func (c *Client) Describe(name, root string) (fd FunctionDescription, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a Function. Name takes precidence. If no name is provided,
|
// Remove a Function. Name takes precidence. If no name is provided,
|
||||||
// the Function defined at root is used.
|
// the Function defined at root is used if it exists.
|
||||||
func (c *Client) Remove(name, root string) error {
|
func (c *Client) Remove(cfg Function) error {
|
||||||
// If name is provided, it takes precidence.
|
// If name is provided, it takes precidence.
|
||||||
// Otherwise load the Function deined at root.
|
// Otherwise load the Function deined at root.
|
||||||
if name != "" {
|
if cfg.Name != "" {
|
||||||
return c.remover.Remove(name)
|
return c.remover.Remove(cfg.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := NewFunction(root)
|
f, err := NewFunction(cfg.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !f.Initialized() {
|
if !f.Initialized() {
|
||||||
return fmt.Errorf("%v is not initialized", f.Name)
|
return fmt.Errorf("Function at %v can not be removed unless initialized. Try removing by name.", f.Root)
|
||||||
}
|
}
|
||||||
return c.remover.Remove(f.Name)
|
return c.remover.Remove(f.Name)
|
||||||
}
|
}
|
||||||
|
@ -567,59 +539,35 @@ func (c *Client) Remove(name, root string) error {
|
||||||
|
|
||||||
type noopBuilder struct{ output io.Writer }
|
type noopBuilder struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopBuilder) Build(_ Function) error {
|
func (n *noopBuilder) Build(_ Function) error { return nil }
|
||||||
fmt.Fprintln(n.output, "skipping build: client not initialized WithBuilder")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopPusher struct{ output io.Writer }
|
type noopPusher struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopPusher) Push(_ Function) error {
|
func (n *noopPusher) Push(_ Function) error { return nil }
|
||||||
fmt.Fprintln(n.output, "skipping push: client not initialized WithPusher")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopDeployer struct{ output io.Writer }
|
type noopDeployer struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopDeployer) Deploy(_ Function) error {
|
func (n *noopDeployer) Deploy(_ Function) error { return nil }
|
||||||
fmt.Fprintln(n.output, "skipping deploy: client not initialized WithDeployer")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopUpdater struct{ output io.Writer }
|
type noopUpdater struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopUpdater) Update(_ Function) error {
|
func (n *noopUpdater) Update(_ Function) error { return nil }
|
||||||
fmt.Fprintln(n.output, "skipping deploy: client not initialized WithDeployer")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopRunner struct{ output io.Writer }
|
type noopRunner struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopRunner) Run(_ Function) error {
|
func (n *noopRunner) Run(_ Function) error { return nil }
|
||||||
fmt.Fprintln(n.output, "skipping run: client not initialized WithRunner")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopRemover struct{ output io.Writer }
|
type noopRemover struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopRemover) Remove(string) error {
|
func (n *noopRemover) Remove(string) error { return nil }
|
||||||
fmt.Fprintln(n.output, "skipping remove: client not initialized WithRemover")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopLister struct{ output io.Writer }
|
type noopLister struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopLister) List() ([]string, error) {
|
func (n *noopLister) List() ([]string, error) { return []string{}, nil }
|
||||||
fmt.Fprintln(n.output, "skipping list: client not initialized WithLister")
|
|
||||||
return []string{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopDNSProvider struct{ output io.Writer }
|
type noopDNSProvider struct{ output io.Writer }
|
||||||
|
|
||||||
func (n *noopDNSProvider) Provide(_ Function) error {
|
func (n *noopDNSProvider) Provide(_ Function) error { return nil }
|
||||||
// Note: at this time manual DNS provisioning required for name -> knative serving netowrk load-balancer
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopProgressListener struct{}
|
type noopProgressListener struct{}
|
||||||
|
|
||||||
|
|
945
client_test.go
945
client_test.go
File diff suppressed because it is too large
Load Diff
|
@ -1 +0,0 @@
|
||||||
package faas
|
|
|
@ -31,11 +31,13 @@ func runDelete(cmd *cobra.Command, args []string) (err error) {
|
||||||
remover := knative.NewRemover()
|
remover := knative.NewRemover()
|
||||||
remover.Verbose = config.Verbose
|
remover.Verbose = config.Verbose
|
||||||
|
|
||||||
|
function := faas.Function{Root: config.Path, Name: config.Name}
|
||||||
|
|
||||||
client := faas.New(
|
client := faas.New(
|
||||||
faas.WithVerbose(verbose),
|
faas.WithVerbose(verbose),
|
||||||
faas.WithRemover(remover))
|
faas.WithRemover(remover))
|
||||||
|
|
||||||
return client.Remove(config.Name, config.Path)
|
return client.Remove(function)
|
||||||
}
|
}
|
||||||
|
|
||||||
type deleteConfig struct {
|
type deleteConfig struct {
|
||||||
|
|
|
@ -19,9 +19,7 @@ func init() {
|
||||||
initCmd.Flags().StringP("trigger", "t", faas.DefaultTrigger, "Function trigger (ex: 'http','events') - $FAAS_TRIGGER")
|
initCmd.Flags().StringP("trigger", "t", faas.DefaultTrigger, "Function trigger (ex: 'http','events') - $FAAS_TRIGGER")
|
||||||
initCmd.Flags().BoolP("yes", "y", false, "When in interactive mode (attached to a TTY), skip prompts. - $FAAS_YES")
|
initCmd.Flags().BoolP("yes", "y", false, "When in interactive mode (attached to a TTY), skip prompts. - $FAAS_YES")
|
||||||
|
|
||||||
var err error
|
if err := initCmd.RegisterFlagCompletionFunc("runtime", CompleteRuntimeList); err != nil {
|
||||||
err = initCmd.RegisterFlagCompletionFunc("runtime", CompleteRuntimeList)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err)
|
fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ory/viper"
|
"github.com/ory/viper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -99,6 +98,8 @@ func newListConfig() listConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEPRECATED BELOW (?):
|
// DEPRECATED BELOW (?):
|
||||||
|
// TODO: regenerate completions, which may necessitate the below change:
|
||||||
|
/*
|
||||||
|
|
||||||
var validFormats []string
|
var validFormats []string
|
||||||
|
|
||||||
|
@ -129,3 +130,4 @@ func fmtYAML(writer io.Writer, names []string) error {
|
||||||
encoder := yaml.NewEncoder(writer)
|
encoder := yaml.NewEncoder(writer)
|
||||||
return encoder.Encode(names)
|
return encoder.Encode(names)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
20
config.go
20
config.go
|
@ -8,12 +8,12 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigFileName is the name of the config's serialized form.
|
// ConfigFile is the name of the config's serialized form.
|
||||||
const ConfigFileName = ".faas.config"
|
const ConfigFile = ".faas.yaml"
|
||||||
|
|
||||||
// Config represents the serialized state of a Function's metadata.
|
// Config represents the serialized state of a Function's metadata.
|
||||||
// See the Function struct for attribute documentation.
|
// See the Function struct for attribute documentation.
|
||||||
type Config struct {
|
type config struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Namespace string `yaml:"namespace"`
|
Namespace string `yaml:"namespace"`
|
||||||
Runtime string `yaml:"runtime"`
|
Runtime string `yaml:"runtime"`
|
||||||
|
@ -26,8 +26,8 @@ type Config struct {
|
||||||
// errors accessing an extant config file, or the contents of the file do not
|
// errors accessing an extant config file, or the contents of the file do not
|
||||||
// unmarshall. A missing file at a valid path does not error but returns the
|
// unmarshall. A missing file at a valid path does not error but returns the
|
||||||
// empty value of Config.
|
// empty value of Config.
|
||||||
func newConfig(root string) (c Config, err error) {
|
func newConfig(root string) (c config, err error) {
|
||||||
filename := filepath.Join(root, ConfigFileName)
|
filename := filepath.Join(root, ConfigFile)
|
||||||
if _, err = os.Stat(filename); os.IsNotExist(err) {
|
if _, err = os.Stat(filename); os.IsNotExist(err) {
|
||||||
err = nil // do not consider a missing config file an error
|
err = nil // do not consider a missing config file an error
|
||||||
return // return the zero value of the config
|
return // return the zero value of the config
|
||||||
|
@ -42,7 +42,7 @@ func newConfig(root string) (c Config, err error) {
|
||||||
|
|
||||||
// fromConfig returns a Function populated from config.
|
// fromConfig returns a Function populated from config.
|
||||||
// Note that config does not include ancillary fields not serialized, such as Root.
|
// Note that config does not include ancillary fields not serialized, such as Root.
|
||||||
func fromConfig(c Config) (f Function) {
|
func fromConfig(c config) (f Function) {
|
||||||
return Function{
|
return Function{
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
Namespace: c.Namespace,
|
Namespace: c.Namespace,
|
||||||
|
@ -52,8 +52,8 @@ func fromConfig(c Config) (f Function) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// toConfig serializes a Function to a config object.
|
// toConfig serializes a Function to a config object.
|
||||||
func toConfig(f Function) Config {
|
func toConfig(f Function) config {
|
||||||
return Config{
|
return config{
|
||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
Namespace: f.Namespace,
|
Namespace: f.Namespace,
|
||||||
Runtime: f.Runtime,
|
Runtime: f.Runtime,
|
||||||
|
@ -63,9 +63,9 @@ func toConfig(f Function) Config {
|
||||||
|
|
||||||
// writeConfig for the given Function out to disk at root.
|
// writeConfig for the given Function out to disk at root.
|
||||||
func writeConfig(f Function) (err error) {
|
func writeConfig(f Function) (err error) {
|
||||||
path := filepath.Join(f.Root, ConfigFileName)
|
path := filepath.Join(f.Root, ConfigFile)
|
||||||
c := toConfig(f)
|
c := toConfig(f)
|
||||||
bb := []byte{}
|
var bb []byte
|
||||||
if bb, err = yaml.Marshal(&c); err != nil {
|
if bb, err = yaml.Marshal(&c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
23
function.go
23
function.go
|
@ -97,6 +97,13 @@ func (f Function) Initialized() bool {
|
||||||
// Default if not provided is --repository (a required global setting)
|
// Default if not provided is --repository (a required global setting)
|
||||||
// followed by the provided (or derived) image name.
|
// followed by the provided (or derived) image name.
|
||||||
func DerivedImage(root, repository string) (image string, err error) {
|
func DerivedImage(root, repository string) (image string, err error) {
|
||||||
|
// Repository is currently required until such time as we support
|
||||||
|
// pushing to an implicitly-available in-cluster registry by default.
|
||||||
|
if repository == "" {
|
||||||
|
err = errors.New("Repository name is required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
f, err := NewFunction(root)
|
f, err := NewFunction(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// an inability to load the funciton means it is not yet initialized
|
// an inability to load the funciton means it is not yet initialized
|
||||||
|
@ -127,6 +134,12 @@ func DerivedImage(root, repository string) (image string, err error) {
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("repository should be either 'namespace' or 'registry/namespace'")
|
err = fmt.Errorf("repository should be either 'namespace' or 'registry/namespace'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicitly append :latest. We currently expect source control to drive
|
||||||
|
// versioning, rather than rely on Docker Hub tags with explicit version
|
||||||
|
// numbers, as is seen in many serverless solutions. This will be updated
|
||||||
|
// to branch name when we add source-driven canary/ bluegreen deployments.
|
||||||
|
image = image + ":latest"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +178,7 @@ func assertEmptyRoot(path string) (err error) {
|
||||||
// contentiousFiles are files which, if extant, preclude the creation of a
|
// contentiousFiles are files which, if extant, preclude the creation of a
|
||||||
// Function rooted in the given directory.
|
// Function rooted in the given directory.
|
||||||
var contentiousFiles = []string{
|
var contentiousFiles = []string{
|
||||||
".faas.yaml",
|
ConfigFile,
|
||||||
".appsody-config.yaml",
|
".appsody-config.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,14 +195,8 @@ func contentiousFilesIn(dir string) (contentious []string, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// effectivelyEmpty directories are those which have no visible files,
|
// effectivelyEmpty directories are those which have no visible files
|
||||||
// and none of the explicitly enumerated contentious files.
|
|
||||||
func isEffectivelyEmpty(dir string) (bool, error) {
|
func isEffectivelyEmpty(dir string) (bool, error) {
|
||||||
// Check for contentious files
|
|
||||||
if contentious, err := contentiousFilesIn(dir); len(contentious) > 0 {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any non-hidden files
|
// Check for any non-hidden files
|
||||||
files, err := ioutil.ReadDir(dir)
|
files, err := ioutil.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
package faas
|
package faas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -77,66 +73,3 @@ func TestPathToDomain(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestApplyConfig ensures that
|
|
||||||
// - a directory with no config has nothing applied without error.
|
|
||||||
// - a directory with an invalid config errors.
|
|
||||||
// - a directory with a valid config has it applied.
|
|
||||||
func TestApplyConfig(t *testing.T) {
|
|
||||||
// Create a temporary directory
|
|
||||||
root := "./testdata/example.com/cfgtest"
|
|
||||||
cfgFile := filepath.Join(root, ConfigFileName)
|
|
||||||
err := os.MkdirAll(root, 0700)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error on TestApplyConfig: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(root)
|
|
||||||
|
|
||||||
c := Function{Name: "staticDefault"}
|
|
||||||
|
|
||||||
// Assert config optional.
|
|
||||||
// Ensure that applying a directory with no config does not error.
|
|
||||||
if err := applyConfig(&c, root); err != nil {
|
|
||||||
t.Fatalf("unexpected error applying a nonexistent config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert an extant, but empty config file causes no errors,
|
|
||||||
// and leaves data intact on the client instance.
|
|
||||||
if err := ioutil.WriteFile(cfgFile, []byte(""), 0644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := applyConfig(&c, root); err != nil {
|
|
||||||
t.Fatalf("unexpected error applying an empty config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert an unparseable config file errors
|
|
||||||
if err := ioutil.WriteFile(cfgFile, []byte("=invalid="), 0644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := applyConfig(&c, root); err == nil {
|
|
||||||
t.Fatal("Did not receive expected error from invalid config.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert a valid config with no value zeroes out the default
|
|
||||||
if err := ioutil.WriteFile(cfgFile, []byte("name:"), 0644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := applyConfig(&c, root); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if c.Name != "" {
|
|
||||||
t.Fatalf("Expected name to be zeroed by config, but got '%v'", c.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert a valid config with a value for name is applied.
|
|
||||||
if err := ioutil.WriteFile(cfgFile, []byte("name: www.example.com"), 0644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := applyConfig(&c, root); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if c.Name != "www.example.com" {
|
|
||||||
t.Fatalf("Expected name 'www.example.com', got '%v'", c.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
|
import "github.com/boson-project/faas"
|
||||||
|
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
BuildInvoked bool
|
BuildInvoked bool
|
||||||
BuildFn func(tag string) (image string, err error)
|
BuildFn func(faas.Function) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBuilder() *Builder {
|
func NewBuilder() *Builder {
|
||||||
return &Builder{
|
return &Builder{
|
||||||
BuildFn: func(string) (string, error) { return "", nil },
|
BuildFn: func(faas.Function) error { return nil },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Builder) Build(tag string) (string, error) {
|
func (i *Builder) Build(f faas.Function) error {
|
||||||
i.BuildInvoked = true
|
i.BuildInvoked = true
|
||||||
return i.BuildFn(tag)
|
return i.BuildFn(f)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
|
import "github.com/boson-project/faas"
|
||||||
|
|
||||||
type Deployer struct {
|
type Deployer struct {
|
||||||
DeployInvoked bool
|
DeployInvoked bool
|
||||||
DeployFn func(name, image string) (address string, err error)
|
DeployFn func(faas.Function) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeployer() *Deployer {
|
func NewDeployer() *Deployer {
|
||||||
return &Deployer{
|
return &Deployer{
|
||||||
DeployFn: func(string, string) (string, error) { return "", nil },
|
DeployFn: func(faas.Function) error { return nil },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Deployer) Deploy(name, image string) (address string, err error) {
|
func (i *Deployer) Deploy(f faas.Function) error {
|
||||||
i.DeployInvoked = true
|
i.DeployInvoked = true
|
||||||
return i.DeployFn(name, image)
|
return i.DeployFn(f)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
|
import "github.com/boson-project/faas"
|
||||||
|
|
||||||
type Pusher struct {
|
type Pusher struct {
|
||||||
PushInvoked bool
|
PushInvoked bool
|
||||||
PushFn func(tag string) error
|
PushFn func(faas.Function) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPusher() *Pusher {
|
func NewPusher() *Pusher {
|
||||||
return &Pusher{
|
return &Pusher{
|
||||||
PushFn: func(tag string) error { return nil },
|
PushFn: func(faas.Function) error { return nil },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Pusher) Push(tag string) error {
|
func (i *Pusher) Push(f faas.Function) error {
|
||||||
i.PushInvoked = true
|
i.PushInvoked = true
|
||||||
return i.PushFn(tag)
|
return i.PushFn(f)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
|
import "github.com/boson-project/faas"
|
||||||
|
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
RunInvoked bool
|
RunInvoked bool
|
||||||
RootRequested string
|
RootRequested string
|
||||||
|
@ -9,8 +11,8 @@ func NewRunner() *Runner {
|
||||||
return &Runner{}
|
return &Runner{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) Run(root string) error {
|
func (r *Runner) Run(f faas.Function) error {
|
||||||
r.RunInvoked = true
|
r.RunInvoked = true
|
||||||
r.RootRequested = root
|
r.RootRequested = f.Root
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
|
import "github.com/boson-project/faas"
|
||||||
|
|
||||||
type Updater struct {
|
type Updater struct {
|
||||||
UpdateInvoked bool
|
UpdateInvoked bool
|
||||||
UpdateFn func(name, image string) error
|
UpdateFn func(faas.Function) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUpdater() *Updater {
|
func NewUpdater() *Updater {
|
||||||
return &Updater{
|
return &Updater{
|
||||||
UpdateFn: func(string, string) error { return nil },
|
UpdateFn: func(faas.Function) error { return nil },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Updater) Update(name, image string) error {
|
func (i *Updater) Update(f faas.Function) error {
|
||||||
i.UpdateInvoked = true
|
i.UpdateInvoked = true
|
||||||
return i.UpdateFn(name, image)
|
return i.UpdateFn(f)
|
||||||
}
|
}
|
||||||
|
|
16
templates.go
16
templates.go
|
@ -20,8 +20,8 @@ import (
|
||||||
// an HTTP Handler ("http") and Cloud Events ("events")
|
// an HTTP Handler ("http") and Cloud Events ("events")
|
||||||
const DefaultTemplate = "http"
|
const DefaultTemplate = "http"
|
||||||
|
|
||||||
// FileAccessor encapsulates methods for accessing template files.
|
// fileAccessor encapsulates methods for accessing template files.
|
||||||
type FileAccessor interface {
|
type fileAccessor interface {
|
||||||
Stat(name string) (os.FileInfo, error)
|
Stat(name string) (os.FileInfo, error)
|
||||||
Open(p string) (file, error)
|
Open(p string) (file, error)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ type file interface {
|
||||||
// When pkger is run, code analysis detects this Include statement,
|
// When pkger is run, code analysis detects this Include statement,
|
||||||
// triggering the serializaation of the templates directory and all
|
// triggering the serializaation of the templates directory and all
|
||||||
// its contents into pkged.go, which is then made available via
|
// its contents into pkged.go, which is then made available via
|
||||||
// a pkger FileAccessor.
|
// a pkger fileAccessor.
|
||||||
// Path is relative to the go module root.
|
// Path is relative to the go module root.
|
||||||
func init() {
|
func init() {
|
||||||
_ = pkger.Include("/templates")
|
_ = pkger.Include("/templates")
|
||||||
|
@ -112,7 +112,7 @@ func (a filesystemAccessor) Open(path string) (file, error) {
|
||||||
return os.Open(path)
|
return os.Open(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copy(src, dest string, accessor FileAccessor) (err error) {
|
func copy(src, dest string, accessor fileAccessor) (err error) {
|
||||||
node, err := accessor.Stat(src)
|
node, err := accessor.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -124,7 +124,7 @@ func copy(src, dest string, accessor FileAccessor) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyNode(src, dest string, accessor FileAccessor) (err error) {
|
func copyNode(src, dest string, accessor fileAccessor) (err error) {
|
||||||
node, err := accessor.Stat(src)
|
node, err := accessor.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -135,7 +135,7 @@ func copyNode(src, dest string, accessor FileAccessor) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
children, err := ReadDir(src, accessor)
|
children, err := readDir(src, accessor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ func copyNode(src, dest string, accessor FileAccessor) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadDir(src string, accessor FileAccessor) ([]os.FileInfo, error) {
|
func readDir(src string, accessor fileAccessor) ([]os.FileInfo, error) {
|
||||||
f, err := accessor.Open(src)
|
f, err := accessor.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -161,7 +161,7 @@ func ReadDir(src string, accessor FileAccessor) ([]os.FileInfo, error) {
|
||||||
return list, nil
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyLeaf(src, dest string, accessor FileAccessor) (err error) {
|
func copyLeaf(src, dest string, accessor fileAccessor) (err error) {
|
||||||
srcFile, err := accessor.Open(src)
|
srcFile, err := accessor.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -6,108 +6,19 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestInitialize ensures that on initialization of a the reference runtime
|
// TestTemplatesEmbeddedFileMode ensures that files from the embedded templates are
|
||||||
// (Go), the template is written.
|
|
||||||
func TestInitialize(t *testing.T) {
|
|
||||||
var (
|
|
||||||
path = "testdata/example.org/www"
|
|
||||||
testFile = "handle.go"
|
|
||||||
template = "http"
|
|
||||||
)
|
|
||||||
err := os.MkdirAll(path, 0744)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(path)
|
|
||||||
|
|
||||||
err = NewInitializer("").Initialize("go", template, path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the directory is not empty
|
|
||||||
if _, err := os.Stat(filepath.Join(path, testFile)); os.IsNotExist(err) {
|
|
||||||
t.Fatalf("Initialize did not result in '%v' being written to '%v'", testFile, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDefaultTemplate ensures that if no template is provided, files are still written.
|
|
||||||
func TestDefaultTemplate(t *testing.T) {
|
|
||||||
var (
|
|
||||||
path = "testdata/example.org/www"
|
|
||||||
testFile = "handle.go"
|
|
||||||
template = ""
|
|
||||||
)
|
|
||||||
err := os.MkdirAll(path, 0744)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(path)
|
|
||||||
|
|
||||||
err = NewInitializer("").Initialize("go", template, path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(path, testFile)); os.IsNotExist(err) {
|
|
||||||
t.Fatalf("Initializing without providing a template did not result in '%v' being written to '%v'", testFile, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestCustom ensures that a custom repository can be used as a template.
|
|
||||||
// Custom repository 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 $FAAS_TEMPLATES, a Go template named 'json'
|
|
||||||
// which is provided in the repository repository 'boson-experimental',
|
|
||||||
// would be expected to be in the location:
|
|
||||||
// $FAAS_TEMPLATES/boson-experimental/go/json
|
|
||||||
// See the CLI for full details, but a standard default location is
|
|
||||||
// $HOME/.config/templates/boson-experimental/go/json
|
|
||||||
func TestCustom(t *testing.T) {
|
|
||||||
var (
|
|
||||||
path = "testdata/example.org/www"
|
|
||||||
testFile = "handle.go"
|
|
||||||
template = "boson-experimental/json"
|
|
||||||
// repos = "testdata/templates"
|
|
||||||
)
|
|
||||||
err := os.MkdirAll(path, 0744)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(path)
|
|
||||||
|
|
||||||
// Unrecognized runtime/template should error
|
|
||||||
err = NewInitializer("").Initialize("go", template, path)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("An unrecognized runtime/template should generate an error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recognized external (non-embedded) path should succeed
|
|
||||||
err = NewInitializer("testdata/templates").Initialize("go", template, path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The template should have been written to the given path.
|
|
||||||
if _, err := os.Stat(filepath.Join(path, testFile)); os.IsNotExist(err) {
|
|
||||||
t.Fatalf("Initializing a custom did not result in the expected '%v' being written to '%v'", testFile, path)
|
|
||||||
} else if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestEmbeddedFileMode ensures that files from the embedded templates are
|
|
||||||
// written with the same mode from whence they came
|
// written with the same mode from whence they came
|
||||||
func TestEmbeddedFileMode(t *testing.T) {
|
func TestTemplatesEmbeddedFileMode(t *testing.T) {
|
||||||
var path = "testdata/example.org/www"
|
var path = "testdata/example.com/www"
|
||||||
err := os.MkdirAll(path, 0744)
|
err := os.MkdirAll(path, 0744)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(path)
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
// Initialize a quarkus app from the embedded templates.
|
client := New()
|
||||||
if err := NewInitializer("").Initialize("quarkus", "events", path); err != nil {
|
function := Function{Root: path, Runtime: "quarkus", Trigger: "events"}
|
||||||
|
if err := client.Initialize(function); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,12 +35,13 @@ func TestEmbeddedFileMode(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCustomFileMode ensures that files from a file-system derived repository
|
// TestTemplatesExtensibleFileMode ensures that files from a file-system
|
||||||
// of templates are written with the same mode from whence they came
|
// derived template is written with mode retained.
|
||||||
func TestFileMode(t *testing.T) {
|
func TestTemplatesExtensibleFileMode(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
path = "testdata/example.org/www"
|
path = "testdata/example.com/www"
|
||||||
template = "boson-experimental/http"
|
template = "boson-experimental/http"
|
||||||
|
templates = "testdata/templates"
|
||||||
)
|
)
|
||||||
err := os.MkdirAll(path, 0744)
|
err := os.MkdirAll(path, 0744)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -137,8 +49,9 @@ func TestFileMode(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(path)
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
// Initialize a quarkus app from the custom repo in ./testdata
|
client := New(WithTemplates(templates))
|
||||||
if err = NewInitializer("testdata/templates").Initialize("quarkus", template, path); err != nil {
|
function := Function{Root: path, Runtime: "quarkus", Trigger: template}
|
||||||
|
if err := client.Initialize(function); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# testdata
|
# testdata
|
||||||
|
|
||||||
Used by tests to hold files necessary for completing and as a place to create
|
Contains test templates and directory targets for domain and subdomain-level tests.
|
||||||
service functions of varying configurations.
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
# region1
|
||||||
|
|
||||||
|
Used as a test target.
|
|
@ -1,3 +0,0 @@
|
||||||
id: "20200507014534.69605855"
|
|
||||||
project-name: www-example-com
|
|
||||||
stack: quay.io/boson/go-ce-functions:0.0
|
|
|
@ -1,2 +0,0 @@
|
||||||
name: www.example.com
|
|
||||||
runtime: go
|
|
|
@ -1,5 +0,0 @@
|
||||||
module function
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require github.com/cloudevents/sdk-go v0.10.2
|
|
|
@ -1,32 +0,0 @@
|
||||||
package function
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
cloudevents "github.com/cloudevents/sdk-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle a CloudEvent.
|
|
||||||
// Supported function signatures:
|
|
||||||
// func()
|
|
||||||
// func() error
|
|
||||||
// func(context.Context)
|
|
||||||
// func(context.Context) error
|
|
||||||
// func(cloudevents.Event)
|
|
||||||
// func(cloudevents.Event) error
|
|
||||||
// func(context.Context, cloudevents.Event)
|
|
||||||
// func(context.Context, cloudevents.Event) error
|
|
||||||
// func(cloudevents.Event, *cloudevents.EventResponse)
|
|
||||||
// func(cloudevents.Event, *cloudevents.EventResponse) error
|
|
||||||
// func(context.Context, cloudevents.Event, *cloudevents.EventResponse)
|
|
||||||
// func(context.Context, cloudevents.Event, *cloudevents.EventResponse) error
|
|
||||||
func Handle(ctx context.Context, event cloudevents.Event) error {
|
|
||||||
if err := event.Validate(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "invalid event received. %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("%v\n", event)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
package function
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
cloudevents "github.com/cloudevents/sdk-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestHandle ensures that Handle accepts a valid CloudEvent without error.
|
|
||||||
func TestHandle(t *testing.T) {
|
|
||||||
// A minimal, but valid, event.
|
|
||||||
event := cloudevents.NewEvent()
|
|
||||||
event.SetID("TEST-EVENT-01")
|
|
||||||
event.SetType("com.example.cloudevents.test")
|
|
||||||
event.SetSource("http://localhost:8080/")
|
|
||||||
|
|
||||||
// Invoke the defined handler.
|
|
||||||
if err := Handle(context.Background(), event); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestHandleInvalid ensures that an invalid input event generates an error.
|
|
||||||
func TestInvalidInput(t *testing.T) {
|
|
||||||
invalidEvent := cloudevents.NewEvent() // missing required fields
|
|
||||||
|
|
||||||
// Attempt to handle the invalid event, ensuring that the handler validats events.
|
|
||||||
if err := Handle(context.Background(), invalidEvent); err == nil {
|
|
||||||
t.Fatalf("handler did not generate error on invalid event. Missing .Validate() check?")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestE2E also tests the Handle function, but does so by creating an actual
|
|
||||||
// CloudEvents HTTP sending and receiving clients. This is a bit redundant
|
|
||||||
// with TestHandle, but illustrates how clients are configured and used.
|
|
||||||
func TestE2E(t *testing.T) {
|
|
||||||
var (
|
|
||||||
receiver cloudevents.Client
|
|
||||||
address string // at which the receiver beings listening (os-chosen port)
|
|
||||||
sender cloudevents.Client // sends an event to the receiver via HTTP
|
|
||||||
handler = Handle // test the user-defined Handler
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if receiver, address, err = newReceiver(t); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sender, err = newSender(t, address); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := receiver.StartReceiver(context.Background(), handler); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, resp, err := sender.Send(context.Background(), newEvent(t, TestData{Sequence: 1, Message: "test message"}))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("OK:\n%v\n", resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestData struct {
|
|
||||||
Sequence int `json:"id"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func newReceiver(t *testing.T) (c cloudevents.Client, address string, err error) {
|
|
||||||
t.Helper()
|
|
||||||
transport, err := cloudevents.NewHTTPTransport(
|
|
||||||
cloudevents.WithPort(0), // use an OS-chosen unused port.
|
|
||||||
cloudevents.WithPath("/"))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
address = fmt.Sprintf("http://127.0.0.1:%v/", transport.GetPort())
|
|
||||||
c, err = cloudevents.NewClient(transport)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSender(t *testing.T, address string) (c cloudevents.Client, err error) {
|
|
||||||
t.Helper()
|
|
||||||
transport, err := cloudevents.NewHTTPTransport(
|
|
||||||
cloudevents.WithTarget(address),
|
|
||||||
cloudevents.WithEncoding(cloudevents.HTTPStructuredV01))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return cloudevents.NewClient(transport, cloudevents.WithTimeNow())
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEvent(t *testing.T, data TestData) (event cloudevents.Event) {
|
|
||||||
source, err := url.Parse("https://example.com/cloudfunction/cloudevent/cmd/runner")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
contentType := "application/json"
|
|
||||||
event = cloudevents.Event{
|
|
||||||
Context: cloudevents.EventContextV01{
|
|
||||||
EventID: "test-event-01",
|
|
||||||
EventType: "com.cloudevents.sample.sent",
|
|
||||||
Source: cloudevents.URLRef{URL: *source},
|
|
||||||
ContentType: &contentType,
|
|
||||||
}.AsV01(),
|
|
||||||
Data: &data,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
Loading…
Reference in New Issue