mirror of https://github.com/knative/func.git
453 lines
13 KiB
Go
453 lines
13 KiB
Go
package faas
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
)
|
|
|
|
const DefaultNamespace = "faas"
|
|
|
|
// Client for a given Service Function.
|
|
type Client struct {
|
|
verbose bool // print verbose logs
|
|
local bool // Run in local-only mode
|
|
internal bool // Deploy without publicly accessible route
|
|
initializer Initializer // Creates initial local function implementation
|
|
builder Builder // Builds a runnable image from function source
|
|
pusher Pusher // Pushes a built image to a registry
|
|
deployer Deployer // Deploys a Service Function
|
|
updater Updater // Updates a deployed Service Function
|
|
runner Runner // Runs the function locally
|
|
remover Remover // Removes remote services
|
|
lister Lister // Lists remote services
|
|
describer Describer
|
|
dnsProvider DNSProvider // Provider of DNS services
|
|
domainSearchLimit int // max dirs to recurse up when deriving domain
|
|
}
|
|
|
|
// Initializer creates the initial/stub Service Function code on first create.
|
|
type Initializer interface {
|
|
// Initialize a Service Function of the given name, using the templates for
|
|
// the given language, written into the given path.
|
|
Initialize(name, language, path string) error
|
|
}
|
|
|
|
// Builder of function source to runnable image.
|
|
type Builder interface {
|
|
// Build a service function of the given name with source located at path.
|
|
// returns the image name built.
|
|
Build(name, path string) (image string, err error)
|
|
}
|
|
|
|
// Pusher of function image to a registry.
|
|
type Pusher interface {
|
|
// Push the image of the service function.
|
|
Push(image string) error
|
|
}
|
|
|
|
// Deployer of function source to running status.
|
|
type Deployer interface {
|
|
// Deploy a service function of given name, using given backing image.
|
|
Deploy(name, image string) (address string, err error)
|
|
}
|
|
|
|
// Updater of a deployed service function with new image.
|
|
type Updater interface {
|
|
// Deploy a service function of given name, using given backing image.
|
|
Update(name, image string) error
|
|
}
|
|
|
|
// Runner runs the function locally.
|
|
type Runner interface {
|
|
// Run the function locally.
|
|
Run(path string) error
|
|
}
|
|
|
|
// Remover of deployed services.
|
|
type Remover interface {
|
|
// Remove the service function from remote.
|
|
Remove(name string) error
|
|
}
|
|
|
|
// Lister of deployed services.
|
|
type Lister interface {
|
|
// List the service functions currently deployed.
|
|
List() ([]string, error)
|
|
}
|
|
|
|
type Subscription struct {
|
|
Source string `json:"source" yaml:"source"`
|
|
Type string `json:"type" yaml:"type"`
|
|
Broker string `json:"broker" yaml:"broker"`
|
|
}
|
|
|
|
type FunctionDescription struct {
|
|
Name string `json:"name" yaml:"name"`
|
|
Routes []string `json:"routes" yaml:"routes"`
|
|
Subscriptions []Subscription `json:"subscriptions" yaml:"subscriptions"`
|
|
}
|
|
|
|
type Describer interface {
|
|
Describe(name string) (description FunctionDescription, err error)
|
|
}
|
|
|
|
// DNSProvider exposes DNS services necessary for serving the Service Function.
|
|
type DNSProvider interface {
|
|
// Provide the given name by routing requests to address.
|
|
Provide(name, address string)
|
|
}
|
|
|
|
// New client for Service Function management.
|
|
func New(options ...Option) (c *Client, err error) {
|
|
// Instantiate client with static defaults.
|
|
c = &Client{
|
|
initializer: &noopInitializer{output: os.Stdout},
|
|
builder: &noopBuilder{output: os.Stdout},
|
|
pusher: &noopPusher{output: os.Stdout},
|
|
deployer: &noopDeployer{output: os.Stdout},
|
|
updater: &noopUpdater{output: os.Stdout},
|
|
runner: &noopRunner{output: os.Stdout},
|
|
remover: &noopRemover{output: os.Stdout},
|
|
lister: &noopLister{output: os.Stdout},
|
|
dnsProvider: &noopDNSProvider{output: os.Stdout},
|
|
domainSearchLimit: -1, // no recursion limit deriving domain by default.
|
|
}
|
|
|
|
// Apply passed options, which take ultimate precidence.
|
|
for _, o := range options {
|
|
o(c)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Option defines a function which when passed to the Client constructor optionally
|
|
// mutates private members at time of instantiation.
|
|
type Option func(*Client)
|
|
|
|
// WithVerbose toggles verbose logging.
|
|
func WithVerbose(v bool) Option {
|
|
return func(c *Client) {
|
|
c.verbose = v
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// WithInitializer provides the concrete implementation of the Service Function
|
|
// initializer (generates stub code on initial create).
|
|
func WithInitializer(i Initializer) Option {
|
|
return func(c *Client) {
|
|
c.initializer = i
|
|
}
|
|
}
|
|
|
|
// WithBuilder provides the concrete implementation of a builder.
|
|
func WithBuilder(d Builder) Option {
|
|
return func(c *Client) {
|
|
c.builder = d
|
|
}
|
|
}
|
|
|
|
// WithPusher provides the concrete implementation of a pusher.
|
|
func WithPusher(d Pusher) Option {
|
|
return func(c *Client) {
|
|
c.pusher = d
|
|
}
|
|
}
|
|
|
|
// WithDeployer provides the concrete implementation of a deployer.
|
|
func WithDeployer(d Deployer) Option {
|
|
return func(c *Client) {
|
|
c.deployer = d
|
|
}
|
|
}
|
|
|
|
// WithUpdater provides the concrete implementation of an updater.
|
|
func WithUpdater(u Updater) Option {
|
|
return func(c *Client) {
|
|
c.updater = u
|
|
}
|
|
}
|
|
|
|
// WithRunner provides the concrete implementation of a deployer.
|
|
func WithRunner(r Runner) Option {
|
|
return func(c *Client) {
|
|
c.runner = r
|
|
}
|
|
}
|
|
|
|
// WithRemover provides the concrete implementation of a remover.
|
|
func WithRemover(r Remover) Option {
|
|
return func(c *Client) {
|
|
c.remover = r
|
|
}
|
|
}
|
|
|
|
// WithLister provides the concrete implementation of a lister.
|
|
func WithLister(l Lister) Option {
|
|
return func(c *Client) {
|
|
c.lister = l
|
|
}
|
|
}
|
|
|
|
func WithDescriber(describer Describer) Option {
|
|
return func(c *Client) {
|
|
c.describer = describer
|
|
}
|
|
}
|
|
|
|
// WithDNSProvider proivdes a DNS provider implementation for registering the
|
|
// effective DNS name which is either explicitly set via WithName or is derived
|
|
// from the root path.
|
|
func WithDNSProvider(provider DNSProvider) Option {
|
|
return func(c *Client) {
|
|
c.dnsProvider = provider
|
|
}
|
|
}
|
|
|
|
// WithDomainSearchLimit sets the maximum levels of upward recursion used when
|
|
// attempting to derive effective DNS name from root path. Ignored if DNS was
|
|
// explicitly set via WithName.
|
|
func WithDomainSearchLimit(limit int) Option {
|
|
return func(c *Client) {
|
|
c.domainSearchLimit = limit
|
|
}
|
|
}
|
|
|
|
// Create a service function of the given language.
|
|
// Name and Root are optional:
|
|
// Name is derived from root if possible.
|
|
// Root is defaulted to the current working directory.
|
|
func (c *Client) Create(language, name, root string) (err error) {
|
|
// Create an instance of a function representation at the given root.
|
|
f, err := NewFunction(root)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Initialize, writing out a template implementation and a config file.
|
|
err = f.Initialize(language, name, c.domainSearchLimit, c.initializer)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Build the now-initialized service function
|
|
image, err := c.builder.Build(f.name, f.root)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// If running local-only, we're done.
|
|
if c.local {
|
|
return
|
|
}
|
|
|
|
// Push the image for the names service to the configured registry
|
|
if err = c.pusher.Push(image); err != nil {
|
|
return
|
|
}
|
|
|
|
// TODO: cluster-local deploy mode
|
|
if c.internal {
|
|
return errors.New("Deploying in cluster-internal mode (no public route) not yet available.")
|
|
}
|
|
|
|
// Deploy the initialized service function, returning its publicly
|
|
// addressible name for possible registration.
|
|
address, err := c.deployer.Deploy(f.name, image)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Ensure that the allocated final address is enabled with the
|
|
// configured DNS provider.
|
|
// NOTE:
|
|
// DNS and TLS are provisioned by Knative Serving + cert-manager,
|
|
// but DNS subdomain CNAME to the Kourier Load Balancer is
|
|
// still manual, and the initial cluster config to suppot the TLD
|
|
// is still manual.
|
|
c.dnsProvider.Provide(f.name, address)
|
|
|
|
// TODO: Create a status structure and return it for clients to use
|
|
// for output, such as from the CLI.
|
|
fmt.Printf("https://%v/\n", f.name)
|
|
|
|
return
|
|
}
|
|
|
|
// Update a previously created service function.
|
|
func (c *Client) Update(root string) (err error) {
|
|
|
|
// Create an instance of a function representation at the given root.
|
|
f, err := NewFunction(root)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if !f.Initialized() {
|
|
// TODO: this needs a test.
|
|
return errors.New(fmt.Sprintf("the given path '%v' does not contain an initialized Service Function. Please create one at this path before updating.", root))
|
|
}
|
|
|
|
// Build an image from the current state of the service function's implementation.
|
|
image, err := c.builder.Build(f.name, f.root)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Push the image for the named service to the configured registry
|
|
if err = c.pusher.Push(image); err != nil {
|
|
return
|
|
}
|
|
|
|
// Update the previously-deployed service function, returning its publicly
|
|
// addressible name for possible registration.
|
|
return c.updater.Update(f.name, image)
|
|
}
|
|
|
|
// Run the function whose code resides at root.
|
|
func (c *Client) Run(root string) error {
|
|
|
|
// Create an instance of a function representation at the given root.
|
|
f, err := NewFunction(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !f.Initialized() {
|
|
// TODO: this needs a test.
|
|
return errors.New(fmt.Sprintf("the given path '%v' does not contain an initialized Service Function. Please create one at this path in order to run.", root))
|
|
}
|
|
|
|
// delegate to concrete implementation of runner entirely.
|
|
return c.runner.Run(f.root)
|
|
}
|
|
|
|
// List currently deployed service functions.
|
|
func (c *Client) List() ([]string, error) {
|
|
// delegate to concrete implementation of lister entirely.
|
|
return c.lister.List()
|
|
}
|
|
|
|
// Describe a function. Name takes precidence. If no name is provided,
|
|
// the function defined at root is used.
|
|
func (c *Client) Describe(name, root string) (fd FunctionDescription, err error) {
|
|
// If name is provided, it takes precidence.
|
|
// Otherwise load the function defined at root.
|
|
if name != "" {
|
|
return c.describer.Describe(name)
|
|
}
|
|
|
|
f, err := NewFunction(root)
|
|
if err != nil {
|
|
return fd, err
|
|
}
|
|
if !f.Initialized() {
|
|
return fd, errors.New(fmt.Sprintf("%v is not initialized", f.name))
|
|
}
|
|
return c.describer.Describe(f.name)
|
|
}
|
|
|
|
// Remove a function. Name takes precidence. If no name is provided,
|
|
// the function defined at root is used.
|
|
func (c *Client) Remove(name, root string) error {
|
|
// If name is provided, it takes precidence.
|
|
// Otherwise load the function deined at root.
|
|
if name != "" {
|
|
return c.remover.Remove(name)
|
|
}
|
|
|
|
f, err := NewFunction(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !f.Initialized() {
|
|
return errors.New(fmt.Sprintf("%v is not initialized", f.name))
|
|
}
|
|
return c.remover.Remove(f.name)
|
|
}
|
|
|
|
// Manual implementations (noops) of required interfaces.
|
|
// In practice, the user of this client package (for example the CLI) will
|
|
// provide a concrete implementation for all of the interfaces. For testing or
|
|
// development, however, it is usefule that they are defaulted to noops and
|
|
// provded only when necessary. Unit tests for the concrete implementations
|
|
// serve to keep the core logic here separate from the imperitive.
|
|
// -----------------------------------------------------
|
|
|
|
type noopInitializer struct{ output io.Writer }
|
|
|
|
func (n *noopInitializer) Initialize(name, language, root string) error {
|
|
fmt.Fprintln(n.output, "skipping initialize: client not initialized WithInitializer")
|
|
return nil
|
|
}
|
|
|
|
type noopBuilder struct{ output io.Writer }
|
|
|
|
func (n *noopBuilder) Build(name, root string) (image string, err error) {
|
|
fmt.Fprintln(n.output, "skipping build: client not initialized WithBuilder")
|
|
return "", nil
|
|
}
|
|
|
|
type noopPusher struct{ output io.Writer }
|
|
|
|
func (n *noopPusher) Push(image string) error {
|
|
fmt.Fprintln(n.output, "skipping push: client not initialized WithPusher")
|
|
return nil
|
|
}
|
|
|
|
type noopDeployer struct{ output io.Writer }
|
|
|
|
func (n *noopDeployer) Deploy(name, image string) (string, error) {
|
|
fmt.Fprintln(n.output, "skipping deploy: client not initialized WithDeployer")
|
|
return "", nil
|
|
}
|
|
|
|
type noopUpdater struct{ output io.Writer }
|
|
|
|
func (n *noopUpdater) Update(name, image string) error {
|
|
fmt.Fprintln(n.output, "skipping deploy: client not initialized WithDeployer")
|
|
return nil
|
|
}
|
|
|
|
type noopRunner struct{ output io.Writer }
|
|
|
|
func (n *noopRunner) Run(root string) error {
|
|
fmt.Fprintln(n.output, "skipping run: client not initialized WithRunner")
|
|
return nil
|
|
}
|
|
|
|
type noopRemover struct{ output io.Writer }
|
|
|
|
func (n *noopRemover) Remove(name string) error {
|
|
fmt.Fprintln(n.output, "skipping remove: client not initialized WithRemover")
|
|
return nil
|
|
}
|
|
|
|
type noopLister struct{ output io.Writer }
|
|
|
|
func (n *noopLister) List() ([]string, error) {
|
|
fmt.Fprintln(n.output, "skipping list: client not initialized WithLister")
|
|
return []string{}, nil
|
|
}
|
|
|
|
type noopDNSProvider struct{ output io.Writer }
|
|
|
|
func (n *noopDNSProvider) Provide(name, address string) {
|
|
// Note: at this time manual DNS provisioning required for name -> knative serving netowrk load-balancer
|
|
}
|