Skip to content

Commit

Permalink
feat: remote template repositories (#437)
Browse files Browse the repository at this point in the history
* feat: remote template repositories

* Update cmd/create.go

Co-authored-by: Lance Ball <lball@redhat.com>

* docs: extensible templates

* feat: remote template repositories

* Update docs/guides/language-packs.md

* Update docs/guides/language-packs.md

Co-authored-by: Lance Ball <lball@redhat.com>

* Update docs/guides/language-packs.md

Co-authored-by: Lance Ball <lball@redhat.com>

* Update docs/guides/templates.md

Co-authored-by: Lance Ball <lball@redhat.com>

Co-authored-by: Lance Ball <lball@redhat.com>
  • Loading branch information
lkingland and lance committed Aug 3, 2021
1 parent 0dba677 commit 9db1a3d
Show file tree
Hide file tree
Showing 32 changed files with 401 additions and 107 deletions.
15 changes: 13 additions & 2 deletions client.go
Expand Up @@ -34,6 +34,7 @@ type Client struct {
describer Describer
dnsProvider DNSProvider // Provider of DNS services
repositories string // path to extensible template repositories
repository string // URL to Git repo (overrides on-disk and embedded)
registry string // default registry for OCI image tags
progressListener ProgressListener // progress listener
emitter Emitter // Emits CloudEvents to functions
Expand Down Expand Up @@ -259,6 +260,15 @@ func WithRepositories(repositories string) Option {
}
}

// WithRepository sets a specific URL to a Git repository from which to pull templates.
// This setting's existence precldes the use of either the inbuilt templates or any
// repositories from the extensible repositories path.
func WithRepository(repository string) Option {
return func(c *Client) {
c.repository = repository
}
}

// WithRegistry sets the default registry which is consulted when an image name/tag
// is not explocitly provided. Can be fully qualified, including the registry
// (ex: 'quay.io/myname') or simply the namespace 'myname' which indicates the
Expand Down Expand Up @@ -342,7 +352,8 @@ func (c *Client) Create(cfg Function) (err error) {
return
}

// Create Function of the given root path.
// Create Function about the given root path.
// Loads extant config if it exists. In-memory representation only.
f, err := NewFunction(cfg.Root)
if err != nil {
return
Expand Down Expand Up @@ -371,7 +382,7 @@ func (c *Client) Create(cfg Function) (err error) {
}

// Write out a template.
w := templateWriter{templates: c.repositories, verbose: c.verbose}
w := templateWriter{repositories: c.repositories, url: c.repository, verbose: c.verbose}
if err = w.Write(f.Runtime, f.Template, f.Root); err != nil {
return
}
Expand Down
51 changes: 35 additions & 16 deletions cmd/create.go
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"os"
"path/filepath"

"github.com/AlecAivazis/survey/v2"
Expand All @@ -27,6 +28,7 @@ func init() {
func newCreateClient(cfg createConfig) *fn.Client {
return fn.New(
fn.WithRepositories(cfg.Repositories),
fn.WithRepository(cfg.Repository),
fn.WithVerbose(cfg.Verbose))
}

Expand Down Expand Up @@ -58,13 +60,13 @@ kn func create --runtime quarkus myfunc
kn func create --template events myfunc
`,
SuggestFor: []string{"vreate", "creaet", "craete", "new"},
PreRunE: bindEnv("runtime", "template", "repositories", "confirm"),
PreRunE: bindEnv("runtime", "template", "repository", "confirm"),
}

cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options (Env: $FUNC_CONFIRM)")
cmd.Flags().StringP("runtime", "l", fn.DefaultRuntime, "Function runtime language/framework. Available runtimes: "+buildpacks.Runtimes()+" (Env: $FUNC_RUNTIME)")
cmd.Flags().StringP("repositories", "r", filepath.Join(configPath(), "repositories"), "Path to extended template repositories (Env: $FUNC_REPOSITORIES)")
cmd.Flags().StringP("template", "t", fn.DefaultTemplate, "Function template. Available templates: 'http' and 'events' (Env: $FUNC_TEMPLATE)")
cmd.Flags().StringP("repository", "r", "", "URI to a Git repository containing the specified template (Env: $FUNC_REPOSITORY)")

// Register tab-completeion function integration
if err := cmd.RegisterFlagCompletionFunc("runtime", CompleteRuntimeList); err != nil {
Expand Down Expand Up @@ -117,11 +119,16 @@ type createConfig struct {
Runtime string

// Repositories is an optional path that, if it exists, will be used as a source
// for additional template repositories not included in the binary. If not provided
// explicitly as a flag (--repositories) or env (FUNC_REPOSITORIES), the default
// location is $XDG_CONFIG_HOME/repositories ($HOME/.config/func/repositories)
// for additional template repositories not included in the binary. provided via
// env (FUNC_REPOSITORIES), the default location is $XDG_CONFIG_HOME/repositories
// ($HOME/.config/func/repositories)
Repositories string

// Repository is the URL of a specific Git repository to use for templates.
// If specified, this takes precidence over both inbuilt templates or
// extensible templates.
Repository string

// Template is the code written into the new Function project, including
// an implementation adhering to one of the supported function signatures.
// May also include additional configuration settings or examples.
Expand All @@ -148,15 +155,24 @@ func newCreateConfig(args []string) createConfig {
}

derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(path)
return createConfig{
Name: derivedName,
Path: derivedPath,
Repositories: viper.GetString("repositories"),
Runtime: viper.GetString("runtime"),
Template: viper.GetString("template"),
Confirm: viper.GetBool("confirm"),
Verbose: viper.GetBool("verbose"),
cc := createConfig{
Name: derivedName,
Path: derivedPath,
Repository: viper.GetString("repository"),
Runtime: viper.GetString("runtime"),
Template: viper.GetString("template"),
Confirm: viper.GetBool("confirm"),
Verbose: viper.GetBool("verbose"),
}

// Repositories not exposed as a flag due to potential confusion and
// unlikliness of being needed, but is left available as an env.
cc.Repositories = os.Getenv("FUNC_REPOSITORIES")
if cc.Repositories == "" {
cc.Repositories = filepath.Join(configPath(), "repositories")
}

return cc
}

// Prompt the user with value of config members, allowing for interaractive changes.
Expand All @@ -165,10 +181,13 @@ func newCreateConfig(args []string) createConfig {
func (c createConfig) Prompt() (createConfig, error) {
if !interactiveTerminal() || !c.Confirm {
// Just print the basics if not confirming
fmt.Printf("Project path: %v\n", c.Path)
fmt.Printf("Project path: %v\n", c.Path)
fmt.Printf("Function name: %v\n", c.Name)
fmt.Printf("Runtime: %v\n", c.Runtime)
fmt.Printf("Template: %v\n", c.Template)
fmt.Printf("Runtime: %v\n", c.Runtime)
fmt.Printf("Template: %v\n", c.Template)
if c.Repository != "" {
fmt.Printf("Repository: %v\n", c.Repository)
}
return c, nil
}

Expand Down
6 changes: 4 additions & 2 deletions docs/guides/commands.md
Expand Up @@ -6,16 +6,18 @@ Creates a new Function project at _`path`_. If _`path`_ is unspecified, assumes

Function name must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?').

The files written upon create include an example Function of the specified language runtime, example tests, and a metadata file `func.yaml`. Together, these are referred to as a Template. Included are the templates 'http' and 'events' (default is 'http') for each language runtime. A template can be pulled from a specific Git repository by providing the `--repository` flag, or from a locally installed repository using the repository's name as a prefix. See the [Templates Guide](templates.md) for more information.

Similar `kn` command: none.

```console
func create <path> [-l <runtime> -t <template>]
func create <path> [-l <runtime> -t <template> -r <repository>]
```

When run as a `kn` plugin.

```console
kn func create <path> [-l <runtime> -t <template>]
kn func create <path> [-l <runtime> -t <template> -r <repository>]
```

## `build`
Expand Down
10 changes: 10 additions & 0 deletions docs/guides/language-packs.md
@@ -0,0 +1,10 @@
# Language Packs

A Language Pack is the mechanism by which the Functions binary can be extended to support additional runtimes, function signatures, even operating systems and installed tooling for a function. A Language Pack includes
- a .builders.yaml file containing a reference to a builder OCI image reference, which conforms to the buildpack builder specification, and contains references to buildpacks supporting this Language Pack
- one or more template directories illustrating the Language Pack's recognized function signatures
- tests and documentation for the templates

Built in to the Functions library is a basic language pack for each supported language.

For an example external language pack, see [https://github.com/lance/gcf-kn/tree/main/ruby]
40 changes: 40 additions & 0 deletions docs/guides/templates.md
@@ -0,0 +1,40 @@
# Templates

When a Function is created, an example implementation and a Function metadata file are written into the new Function's working directory. Together, these files are referred to as the Function's Template. Included are the templates 'http' and 'events' for each supported language runtime.

These embedded templates are minimal by design. The Function contains a minimum of external dependencies, and the 'func.yaml' defines a final environment within which the Funciton will execute that is devoid of any extraneous packages or services.

To make use of more complex inital Function implementions, or to define runtime environments with arbitrarily complex requirements, the templates system is fully pluggable.

## External Git Repositories

When creating a new Function, a Git repository can be specified as the source for the template files. For example, the Boson Project maintains a set of example Functions at https://github.com/boson-project/templates which can be used during project creation.

For example, the Boson Project Templates repository contains an example "Hello World" Function implementation in each of the officially supported languages. To use this template via the CLI, use the flags:

func create <name> --template hello-world --repository https://github.com/boson-project/templates

## Locally Installing Repositories

Template Repositories can also be installed locally by placing them in the Functions configuration directory.

To install the Boson Project templates locally, for example, clone the repository and name it `boson` using `git clone https://github.com/boson-project/templats ~/.config/func/repositories/boson`

Once installed, the Boson Hello World template can be specified:

func create <name> --template boson/hello-world

## Language Packs

In addition to example implementations, a template includes a `func.yaml` which includes metadata about the Function. By default this is populated with things like the new Function's name. It also includes a reference to the specific tooling which compiles and packages the Function into its deployable form. This is called the Builder. By customizing this metadata, it is more than just a template; it is referred to as a Language Pack. See [Project Configuration with func.yaml](func_yaml.md).

A Language Pack can support additional function signatures and can fully customize the environment of the final running Function. For more information see the [Language Pack Guide](language-packs.md).









2 changes: 2 additions & 0 deletions go.mod
Expand Up @@ -10,6 +10,8 @@ require (
github.com/docker/docker v20.10.7+incompatible
github.com/docker/docker-credential-helpers v0.6.4
github.com/docker/go-connections v0.4.0
github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2
github.com/google/go-cmp v0.5.6
github.com/google/uuid v1.2.0
github.com/markbates/pkger v0.17.1
Expand Down

0 comments on commit 9db1a3d

Please sign in to comment.