Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch extension config file based on languages #125

Merged
merged 14 commits into from
Mar 25, 2021
7 changes: 7 additions & 0 deletions data/example/init/templates/configurations/extension.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// We recommend to use json as the configuration format,
mathetake marked this conversation as resolved.
Show resolved Hide resolved
// however, some languages (e.g. TinyGo) does not support ready-to-use json library as of now.
// As a temporary alternative, we use ".txt" format.
//
// Note that when using ".txt" format, lines starting with "//" are entirely discarded by GetEnvoy,
// and never passed to Envoy in "getenvoy extension run" command.
// Therefore, the lines above (including this line) are ignored.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
| ----------------- | ------------------------ | ----------------------------------------------------------------------- |
| `example.yaml` | `Example` descriptor | Describes runtime requirements, e.g. a specific version of `Envoy` |
| `envoy.tmpl.yaml` | `Envoy` bootstrap config | Provides `Envoy` config that demoes extension in action |
| `extension.json` | `Extension` config | Provides configuration for extension itself |
| `EXTENSION_CONFIG_FILE_NAME` | `Extension` config | Provides configuration for extension itself |
mathetake marked this conversation as resolved.
Show resolved Hide resolved

## Components

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
| ----------------- | ------------------------ | ----------------------------------------------------------------------- |
| `example.yaml` | `Example` descriptor | Describes runtime requirements, e.g. a specific version of `Envoy` |
| `envoy.tmpl.yaml` | `Envoy` bootstrap config | Provides `Envoy` config that demoes extension in action |
| `extension.json` | `Extension` config | Provides configuration for extension itself |
| `EXTENSION_CONFIG_FILE_NAME` | `Extension` config | Provides configuration for extension itself |
mathetake marked this conversation as resolved.
Show resolved Hide resolved

## Components

Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
| ----------------- | ------------------------ | ----------------------------------------------------------------------- |
| `example.yaml` | `Example` descriptor | Describes runtime requirements, e.g. a specific version of `Envoy` |
| `envoy.tmpl.yaml` | `Envoy` bootstrap config | Provides `Envoy` config that demoes extension in action |
| `extension.json` | `Extension` config | Provides configuration for extension itself |
| `EXTENSION_CONFIG_FILE_NAME` | `Extension` config | Provides configuration for extension itself |
mathetake marked this conversation as resolved.
Show resolved Hide resolved

## Components

Expand Down
Empty file.
47 changes: 46 additions & 1 deletion pkg/cmd/extension/example/cmd_add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,18 @@ Run 'getenvoy extension examples add --help' for usage.
Done!
`))
By("verifying file system")
Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/README.md")).To(BeAnExistingFile())
readmePath := filepath.Join(tempDir, ".getenvoy/extension/examples/default/README.md")
Expect(readmePath).To(BeAnExistingFile())
Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/envoy.tmpl.yaml")).To(BeAnExistingFile())
Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/example.yaml")).To(BeAnExistingFile())
Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/extension.json")).To(BeAnExistingFile())

// Check README substitution: EXTENSION_CONFIG_FILE_NAME must be replaced with "extension.json".
data, err := ioutil.ReadFile(readmePath)
Expect(err).ToNot(HaveOccurred())
readme := string(data)
Expect(readme).To(ContainSubstring("extension.json"))
Expect(readme).To(Not(ContainSubstring("EXTENSION_CONFIG_FILE_NAME")))
mathetake marked this conversation as resolved.
Show resolved Hide resolved
})

It("should create example setup with a given --name", func() {
Expand Down Expand Up @@ -186,6 +194,43 @@ Done!
Run 'getenvoy extension examples add --help' for usage.
`))
})

It("should create 'default' example setup when no --name is omitted for TinyGo", func() {
By("simulating a workspace without any examples")
err := copy.Copy("testdata/workspace4", tempDir)
Expect(err).NotTo(HaveOccurred())

By("changing to a workspace dir")
chdir(tempDir)

By("running command")
c.SetArgs([]string{"extension", "examples", "add"})
err = cmdutil.Execute(c)
Expect(err).ToNot(HaveOccurred())

By("verifying command output")
Expect(stdout.String()).To(BeEmpty())
Expect(stderr.String()).To(Equal(`Scaffolding a new example setup:
* .getenvoy/extension/examples/default/README.md
* .getenvoy/extension/examples/default/envoy.tmpl.yaml
* .getenvoy/extension/examples/default/example.yaml
* .getenvoy/extension/examples/default/extension.txt
Done!
`))
By("verifying file system")
readmePath := filepath.Join(tempDir, ".getenvoy/extension/examples/default/README.md")
Expect(readmePath).To(BeAnExistingFile())
Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/envoy.tmpl.yaml")).To(BeAnExistingFile())
Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/example.yaml")).To(BeAnExistingFile())
Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/extension.txt")).To(BeAnExistingFile())

// Check README substitution: EXTENSION_CONFIG_FILE_NAME must be replaced with "extension.txt".
data, err := ioutil.ReadFile(readmePath)
Expect(err).ToNot(HaveOccurred())
readme := string(data)
Expect(readme).To(ContainSubstring("extension.txt"))
Expect(readme).To(Not(ContainSubstring("EXTENSION_CONFIG_FILE_NAME")))
})
})

Context("outside of a workspace directory", func() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#
# Envoy Wasm extension created with getenvoy toolkit.
#
kind: Extension

name: me.filters.http.my_http_filter

category: envoy.filters.http
language: tinygo

# Runtime the extension is being developed against.
runtime:
envoy:
version: standard:1.17.0
37 changes: 37 additions & 0 deletions pkg/cmd/extension/run/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,43 @@ Run 'getenvoy extension run --help' for usage.
Done!
docker stderr
envoy stderr
`))

By("verifying Envoy config")
bootstrap := envoyCaptured.readFileToJSON("envoy.tmpl.yaml")
Expect(bootstrap).NotTo(BeEmpty())
})

It("should create default example if missing for TinyGo", func() {
By("simulating a workspace without 'default' example")
tempDir, err := ioutil.TempDir("", "")
Expect(err).NotTo(HaveOccurred())
defer func() {
Expect(os.RemoveAll(tempDir)).To(Succeed())
}()
err = copy.Copy("testdata/workspace_tinygo", tempDir)
Expect(err).NotTo(HaveOccurred())

By("changing to a workspace dir")
workspaceDir := chdir(tempDir)

By("running command")
c.SetArgs([]string{"extension", "run"})
err = cmdutil.Execute(c)
Expect(err).ToNot(HaveOccurred())

By("verifying command output")
Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-tinygo-builder:latest build --output-file build/extension.wasm
%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml
`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd())))
Expect(stderr.String()).To(Equal(`Scaffolding a new example setup:
* .getenvoy/extension/examples/default/README.md
* .getenvoy/extension/examples/default/envoy.tmpl.yaml
* .getenvoy/extension/examples/default/example.yaml
* .getenvoy/extension/examples/default/extension.txt
Done!
docker stderr
envoy stderr
`))

By("verifying Envoy config")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#
# Envoy Wasm extension created with getenvoy toolkit.
#
kind: Extension

name: mycompany.filters.http.mytest

category: envoy.filters.http
language: tinygo

# Runtime the extension is being developed against.
runtime:
envoy:
version: standard:1.17.0
4 changes: 2 additions & 2 deletions pkg/extension/example/init/registry/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ func globalRegistry() registry {

// Get returns an example template registered in a global registry for a given
// extension category and name.
mathetake marked this conversation as resolved.
Show resolved Hide resolved
func Get(category extension.Category, name string) (*Entry, error) {
return globalRegistry().Get(category, name)
func Get(descriptor *extension.Descriptor, name string) (*Entry, error) {
return globalRegistry().Get(descriptor, name)
}
41 changes: 37 additions & 4 deletions pkg/extension/example/init/registry/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import (
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/shurcooL/httpfs/vfsutil"
Expand All @@ -30,7 +32,7 @@ import (
// registry represents a registry of example templates.
type registry interface {
// Get returns a registry entry.
Get(category extension.Category, example string) (*Entry, error)
Get(category *extension.Descriptor, example string) (*Entry, error)
mathetake marked this conversation as resolved.
Show resolved Hide resolved
}

// fsRegistry represents a registry of example templates backed by
Expand All @@ -40,8 +42,8 @@ type fsRegistry struct {
namingScheme func(category extension.Category, example string) string
}

func (r *fsRegistry) Get(category extension.Category, example string) (*Entry, error) {
dirName := r.namingScheme(category, example)
func (r *fsRegistry) Get(descriptor *extension.Descriptor, example string) (*Entry, error) { //nolint
mathetake marked this conversation as resolved.
Show resolved Hide resolved
dirName := r.namingScheme(descriptor.Category, example)
dir, err := r.fs.Open(dirName)
if err != nil {
return nil, errors.Wrapf(err, "failed to open: %s", dirName)
Expand All @@ -54,15 +56,39 @@ func (r *fsRegistry) Get(category extension.Category, example string) (*Entry, e
if !info.IsDir() {
return nil, errors.Errorf("%q is not a directory", dirName)
}

return &Entry{
Category: category,
Category: descriptor.Category,
Name: example,
NewExample: func(*extension.Descriptor) (model.Example, error) {
fileNames, err := listFiles(r.fs, dirName)
if err != nil {
return nil, errors.Wrapf(err, "failed to list files in a directory: %s", dirName)
}
fileSet := model.NewFileSet()

// Add extension config file.
var extensionConfigFileName string
switch descriptor.Language {
case extension.LanguageTinyGo:
extensionConfigFileName = "extension.txt"
default:
extensionConfigFileName = "extension.json"
}
source := path.Join("/configurations", extensionConfigFileName)
f, err := r.fs.Open(source)
if err != nil {
return nil, errors.Wrapf(err, "failed to add extension config file")
}
defer f.Close() //nolint
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to add extension config file")
}

// Write to the example dir.
fileSet.Add(extensionConfigFileName, &model.File{Source: source, Content: data})

for _, fileName := range fileNames {
file, err := r.fs.Open(fileName)
if err != nil {
Expand All @@ -77,6 +103,13 @@ func (r *fsRegistry) Get(category extension.Category, example string) (*Entry, e
if err != nil {
return nil, err
}

// Need to adjust according to the extension config file name.
// See https://github.com/tetratelabs/getenvoy/issues/124
if relPath == "README.md" {
data = []byte(strings.ReplaceAll(string(data),
"EXTENSION_CONFIG_FILE_NAME", extensionConfigFileName))
}
fileSet.Add(relPath, &model.File{Source: fileName, Content: data})
}
return model.NewExample(fileSet)
Expand Down
40 changes: 30 additions & 10 deletions pkg/extension/workspace/example/runtime/configdir/configdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,25 @@
package configdir

import (
"bufio"
"bytes"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"

"github.com/pkg/errors"
"strings"

envoybootstrap "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
"github.com/pkg/errors"

"github.com/tetratelabs/getenvoy/pkg/extension/manager"
"github.com/tetratelabs/getenvoy/pkg/extension/workspace/example/envoy/template"
"github.com/tetratelabs/getenvoy/pkg/extension/workspace/example/envoy/util"
"github.com/tetratelabs/getenvoy/pkg/extension/workspace/example/runtime"
"github.com/tetratelabs/getenvoy/pkg/extension/workspace/model"
"github.com/tetratelabs/multierror"

osutil "github.com/tetratelabs/getenvoy/pkg/util/os"
"github.com/tetratelabs/multierror"
)

// NewConfigDir creates a config directory for a single example run.
Expand Down Expand Up @@ -106,7 +108,7 @@ func (d *configDir) init() error {
return nil
}

// process resolves placehoders in the Envoy bootstrap config, including
// process resolves placeholders in the Envoy bootstrap config, including
// 1) placeholders in the bootstrap file (envoy.tmpl.yaml or envoy.tmpl.json)
// 2) (optional) placeholders in a LDS file (value of `bootstrap.dynamic_resources.lds_config.path`)
// 3) (optional) placeholders in a CDS file (value of `bootstrap.dynamic_resources.cds_config.path`)
Expand All @@ -116,7 +118,7 @@ func (d *configDir) process() error {
return err
}

// resolve placehoders in the bootstrap file
// resolve placeholders in the bootstrap file
bootstrapFileName, bootstrapFile := d.ctx.Opts.Example.GetEnvoyConfig()
bootstrapContent, err := d.processEnvoyTemplate(bootstrapFile, expandContext)
if err != nil {
Expand All @@ -138,14 +140,14 @@ func (d *configDir) process() error {
}
d.bootstrap = &bootstrap

// resolve placehoders in the LDS file
// resolve placeholders in the LDS file
if fileName := d.bootstrap.GetDynamicResources().GetLdsConfig().GetPath(); fileName != "" {
if err := d.processEnvoyXdsFile(fileName, expandContext); err != nil {
return err
}
}

// resolve placehoders in the CDS file
// resolve placeholders in the CDS file
if fileName := d.bootstrap.GetDynamicResources().GetCdsConfig().GetPath(); fileName != "" {
if err := d.processEnvoyXdsFile(fileName, expandContext); err != nil {
return err
Expand All @@ -159,11 +161,29 @@ func (d *configDir) newExpandContext() (*template.ExpandContext, error) {
if err != nil {
return nil, errors.Wrapf(err, "failed to resolve absolute path of a *.wasm file %q", d.ctx.Opts.Extension.WasmFile)
}
configuration := string(d.ctx.Opts.GetExtensionConfig().Content)

var config string
// For .txt format, we discard lines starts with "//".
if path.Ext(d.ctx.Opts.GetExtensionConfig().Source) == ".txt" {
var configLines []string
scanner := bufio.NewScanner(bytes.NewReader(d.ctx.Opts.GetExtensionConfig().Content))
for scanner.Scan() {
line := scanner.Text()

if strings.HasPrefix(line, "//") {
continue
}

configLines = append(configLines, line)
}
config = strings.Join(configLines, "\n")
} else {
config = string(d.ctx.Opts.GetExtensionConfig().Content)
}
mathetake marked this conversation as resolved.
Show resolved Hide resolved

return &template.ExpandContext{
DefaultExtension: manager.NewLocalExtension(d.ctx.Opts.Workspace.GetExtensionDescriptor(), wasmFile),
DefaultExtensionConfig: configuration,
DefaultExtensionConfig: config,
}, nil
}

Expand Down
Loading