Skip to content

Commit

Permalink
Adds FUNC_E_PLATFORM to override host OS and architecture used to run…
Browse files Browse the repository at this point in the history
… Envoy (#300)

Signed-off-by: Karl Cardenas <[email protected]>
Co-authored-by: Adrian Cole <[email protected]>
  • Loading branch information
karl-cardenas-coding and Adrian Cole authored Jul 10, 2021
1 parent ce56566 commit 94a39ab
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 33 deletions.
5 changes: 5 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ You may want to override `$ENVOY_VERSIONS_URL` to supply custom builds or
otherwise control the source of Envoy binaries. When overriding, validate
your JSON first: https://archive.tetratelabs.io/release-versions-schema.json

Advanced:
`FUNC_E_PLATFORM` overrides the host OS and architecture of Envoy binaries.
This value must be constant within a `$FUNC_E_HOME`.

# Commands

| Name | Usage |
Expand All @@ -26,3 +30,4 @@ your JSON first: https://archive.tetratelabs.io/release-versions-schema.json
| ---- | ----- | ------- |
| FUNC_E_HOME | func-e home directory (location of installed versions and run archives) | ${HOME}/.func-e |
| ENVOY_VERSIONS_URL | URL of Envoy versions JSON | https://archive.tetratelabs.io/envoy/envoy-versions.json |
| FUNC_E_PLATFORM | the host OS and architecture of Envoy binaries. Ex. darwin/arm64 | $GOOS/$GOARCH |
30 changes: 27 additions & 3 deletions internal/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
// NewApp create a new root command. The globals.GlobalOpts parameter allows tests to scope overrides, which avoids
// having to define a flag for everything needed in tests.
func NewApp(o *globals.GlobalOpts) *cli.App {
var envoyVersionsURL, homeDir string
var envoyVersionsURL, homeDir, platform string
lastKnownEnvoy := getLastKnownEnvoy(o)

app := cli.NewApp()
Expand All @@ -47,7 +47,11 @@ func NewApp(o *globals.GlobalOpts) *cli.App {
You may want to override ` + "`$ENVOY_VERSIONS_URL`" + ` to supply custom builds or
otherwise control the source of Envoy binaries. When overriding, validate
your JSON first: https://archive.tetratelabs.io/release-versions-schema.json`
your JSON first: https://archive.tetratelabs.io/release-versions-schema.json
Advanced:
` + "`FUNC_E_PLATFORM`" + ` overrides the host OS and architecture of Envoy binaries.
This value must be constant within a ` + "`$FUNC_E_HOME`" + `.`
app.Version = string(o.Version)
app.Flags = []cli.Flag{
&cli.StringFlag{
Expand All @@ -63,8 +67,17 @@ func NewApp(o *globals.GlobalOpts) *cli.App {
DefaultText: globals.DefaultEnvoyVersionsURL,
Destination: &envoyVersionsURL,
EnvVars: []string{"ENVOY_VERSIONS_URL"},
}}
},
&cli.StringFlag{
Name: "platform",
Usage: "the host OS and architecture of Envoy binaries. Ex. darwin/arm64",
DefaultText: "$GOOS/$GOARCH",
Destination: &platform,
EnvVars: []string{"FUNC_E_PLATFORM"},
},
}
app.Before = func(c *cli.Context) error {
setPlatform(o, platform)
if err := setHomeDir(o, homeDir); err != nil {
return err
}
Expand Down Expand Up @@ -103,6 +116,17 @@ var helpCommand = &cli.Command{
},
}

func setPlatform(o *globals.GlobalOpts, platform string) {
if o.Platform != "" { // overridden for tests
return
}
if platform != "" { // set by user
o.Platform = version.Platform(platform)
} else {
o.Platform = globals.DefaultPlatform
}
}

func setEnvoyVersionsURL(o *globals.GlobalOpts, versionsURL string) error {
if o.EnvoyVersionsURL != "" { // overridden for tests
return nil
Expand Down
43 changes: 43 additions & 0 deletions internal/cmd/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,49 @@ func TestHomeDir(t *testing.T) {
}
}

func TestPlatformArg(t *testing.T) {
type testCase struct {
name string
args []string
// setup returns a tear-down function
setup func() func()
expected version.Platform
}

tests := []testCase{
{
name: "FUNC_E_PLATFORM env",
args: []string{"func-e"},
setup: func() func() {
return morerequire.RequireSetenv(t, "FUNC_E_PLATFORM", "linux/amd64")
},
expected: version.Platform("linux/amd64"),
},
{
name: "--platform flag",
args: []string{"func-e", "--platform", "darwin/amd64"},
expected: version.Platform("darwin/amd64"),
},
}

for _, tc := range tests {
tc := tc // pin! see https://github.com/kyoh86/scopelint for why

t.Run(tc.name, func(t *testing.T) {
if tc.setup != nil {
tearDown := tc.setup()
defer tearDown()
}

o := &globals.GlobalOpts{}
err := runTestCommand(t, o, tc.args)
require.NoError(t, err)
require.Equal(t, tc.expected, o.Platform)
})
}

}

func TestEnvoyVersionsURL(t *testing.T) {
type testCase struct {
name string
Expand Down
8 changes: 4 additions & 4 deletions internal/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ state. On exit, these archive into ` + "`$FUNC_E_HOME/runs/$epochtime.tar.gz`",
return nil
},
Action: func(c *cli.Context) error {
if err := initializeRunOpts(c.Context, o, globals.CurrentPlatform, envoyVersion); err != nil {
if err := initializeRunOpts(c.Context, o, envoyVersion); err != nil {
return err
}
r := envoy.NewRuntime(&o.RunOpts)
Expand Down Expand Up @@ -111,10 +111,10 @@ state. On exit, these archive into ` + "`$FUNC_E_HOME/runs/$epochtime.tar.gz`",
// initializeRunOpts allows us to default values when not overridden for tests.
// The version parameter correlates with the globals.GlobalOpts EnvoyPath which is installed if needed.
// Notably, this creates and sets a globals.GlobalOpts WorkingDirectory for Envoy, and any files that precede it.
func initializeRunOpts(ctx context.Context, o *globals.GlobalOpts, p version.Platform, v version.Version) error {
func initializeRunOpts(ctx context.Context, o *globals.GlobalOpts, v version.Version) error {
runOpts := &o.RunOpts
if o.EnvoyPath == "" { // not overridden for tests
envoyPath, err := envoy.InstallIfNeeded(ctx, o, p, v)
envoyPath, err := envoy.InstallIfNeeded(ctx, o, v)
if err != nil {
return err
}
Expand Down Expand Up @@ -144,7 +144,7 @@ func setHomeEnvoyVersion(ctx context.Context, o *globals.GlobalOpts) error {

// First time install: look up the latest version, which may be newer than version.LastKnownEnvoy!
fmt.Fprintln(o.Out, "looking up latest version") //nolint
m, err := envoy.FuncEVersions(ctx, o.EnvoyVersionsURL, globals.CurrentPlatform, o.Version)
m, err := envoy.FuncEVersions(ctx, o.EnvoyVersionsURL, o.Platform, o.Version)
if err != nil {
return NewValidationError(`couldn't read latest version from %s: %s`, o.EnvoyVersionsURL, err)
}
Expand Down
5 changes: 5 additions & 0 deletions internal/cmd/testdata/func-e_help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ USAGE:
otherwise control the source of Envoy binaries. When overriding, validate
your JSON first: https://archive.tetratelabs.io/release-versions-schema.json

Advanced:
`FUNC_E_PLATFORM` overrides the host OS and architecture of Envoy binaries.
This value must be constant within a `$FUNC_E_HOME`.

VERSION:
1.0

Expand All @@ -25,4 +29,5 @@ COMMANDS:
GLOBAL OPTIONS:
--home-dir value func-e home directory (location of installed versions and run archives) (default: ${HOME}/.func-e) [$FUNC_E_HOME]
--envoy-versions-url value URL of Envoy versions JSON (default: https://archive.tetratelabs.io/envoy/envoy-versions.json) [$ENVOY_VERSIONS_URL]
--platform value the host OS and architecture of Envoy binaries. Ex. darwin/arm64 (default: $GOOS/$GOARCH) [$FUNC_E_PLATFORM]
--version, -v print the version (default: false)
2 changes: 1 addition & 1 deletion internal/cmd/use.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ $ func-e use %s`, envoy.CurrentVersionWorkingDirFile, envoy.CurrentVersionHomeDi
Before: validateVersionArg,
Action: func(c *cli.Context) error {
v := version.Version(c.Args().First())
if _, err := envoy.InstallIfNeeded(c.Context, o, globals.CurrentPlatform, v); err != nil {
if _, err := envoy.InstallIfNeeded(c.Context, o, v); err != nil {
return err
}
return envoy.WriteCurrentVersion(v, o.HomeDir)
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ func NewVersionsCmd(o *globals.GlobalOpts) *cli.Command {
currentVersion, currentVersionSource, _ := envoy.CurrentVersion(o.HomeDir)

if c.Bool("all") {
if ev, err := envoy.FuncEVersions(c.Context, o.EnvoyVersionsURL, globals.CurrentPlatform, o.Version); err != nil {
if ev, err := envoy.FuncEVersions(c.Context, o.EnvoyVersionsURL, o.Platform, o.Version); err != nil {
return err
} else if err := addAvailableVersions(&rows, ev.Versions, globals.CurrentPlatform); err != nil {
} else if err := addAvailableVersions(&rows, ev.Versions, o.Platform); err != nil {
return err
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/envoy/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestHttpGet_AddsDefaultHeaders(t *testing.T) {
}))
defer ts.Close()

res, err := httpGet(context.Background(), ts.URL, globals.CurrentPlatform, "dev")
res, err := httpGet(context.Background(), ts.URL, globals.DefaultPlatform, "dev")
require.NoError(t, err)

defer res.Body.Close()
Expand Down
16 changes: 8 additions & 8 deletions internal/envoy/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,39 +35,39 @@ import (
var binEnvoy = filepath.Join("bin", "envoy"+moreos.Exe)

// InstallIfNeeded downloads an Envoy binary corresponding to the given version and returns a path to it or an error.
func InstallIfNeeded(ctx context.Context, o *globals.GlobalOpts, p version.Platform, v version.Version) (string, error) {
func InstallIfNeeded(ctx context.Context, o *globals.GlobalOpts, v version.Version) (string, error) {
installPath := filepath.Join(o.HomeDir, "versions", string(v))
envoyPath := filepath.Join(installPath, binEnvoy)
_, err := os.Stat(envoyPath)
switch {
case os.IsNotExist(err):
var ev version.ReleaseVersions // Get version metadata for what we will install
ev, err = FuncEVersions(ctx, o.EnvoyVersionsURL, p, v)
ev, err = FuncEVersions(ctx, o.EnvoyVersionsURL, o.Platform, v)
if err != nil {
return "", err
}

tarballURL := ev.Versions[v].Tarballs[p] // Ensure there is a version for this platform
tarballURL := ev.Versions[v].Tarballs[o.Platform] // Ensure there is a version for this platform
if tarballURL == "" {
return "", fmt.Errorf("couldn't find version %q for platform %q", v, p)
return "", fmt.Errorf("couldn't find version %q for platform %q", v, o.Platform)
}

tarball := version.Tarball(path.Base(string(tarballURL)))
sha256Sum := ev.SHA256Sums[tarball]
if len(sha256Sum) != 64 {
return "", fmt.Errorf("couldn't find sha256Sum of version %q for platform %q: %w", v, p, err)
return "", fmt.Errorf("couldn't find sha256Sum of version %q for platform %q: %w", v, o.Platform, err)
}

var mtime time.Time // Create a directory for the version, preserving the release date as its mtime
if mtime, err = time.Parse("2006-01-02", string(ev.Versions[v].ReleaseDate)); err != nil {
return "", fmt.Errorf("couldn't find releaseDate of version %q for platform %q: %w", v, p, err)
return "", fmt.Errorf("couldn't find releaseDate of version %q for platform %q: %w", v, o.Platform, err)
}
if err = os.MkdirAll(installPath, 0750); err != nil {
return "", fmt.Errorf("unable to create directory %q: %w", installPath, err)
}

fmt.Fprintln(o.Out, "downloading", tarballURL) //nolint
if err = untarEnvoy(ctx, installPath, tarballURL, sha256Sum, p, v); err != nil { //nolint
fmt.Fprintln(o.Out, "downloading", tarballURL) //nolint
if err = untarEnvoy(ctx, installPath, tarballURL, sha256Sum, o.Platform, v); err != nil { //nolint
return "", err
}
if err = os.Chtimes(installPath, mtime, mtime); err != nil { // overwrite the mtime to preserve it in the list
Expand Down
28 changes: 16 additions & 12 deletions internal/envoy/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ func TestUntarEnvoyError(t *testing.T) {

url := version.TarballURL(server.URL + "/file.tar.gz")
t.Run("error on incorrect URL", func(t *testing.T) {
err := untarEnvoy(ctx, dst, url, tarballSHA256sum, globals.CurrentPlatform, "dev")
err := untarEnvoy(ctx, dst, url, tarballSHA256sum, globals.DefaultPlatform, "dev")
require.EqualError(t, err, fmt.Sprintf(`received 404 status code from %s`, url))
})

realHandler = func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(200)
}
t.Run("error on empty", func(t *testing.T) {
err := untarEnvoy(ctx, dst, url, tarballSHA256sum, globals.CurrentPlatform, "dev")
err := untarEnvoy(ctx, dst, url, tarballSHA256sum, globals.DefaultPlatform, "dev")
require.EqualError(t, err, fmt.Sprintf(`error untarring %s: EOF`, url))
})

Expand All @@ -71,7 +71,7 @@ func TestUntarEnvoyError(t *testing.T) {
w.Write([]byte("mary had a little lamb")) //nolint
}
t.Run("error on not a tar", func(t *testing.T) {
err := untarEnvoy(ctx, dst, url, tarballSHA256sum, globals.CurrentPlatform, "dev")
err := untarEnvoy(ctx, dst, url, tarballSHA256sum, globals.DefaultPlatform, "dev")
require.EqualError(t, err, fmt.Sprintf(`error untarring %s: gzip: invalid header`, url))
})

Expand All @@ -80,7 +80,7 @@ func TestUntarEnvoyError(t *testing.T) {
w.Write(tarball) //nolint
}
t.Run("error on wrong sha256sum a tar", func(t *testing.T) {
err := untarEnvoy(ctx, dst, url, "cafebabe", globals.CurrentPlatform, "dev")
err := untarEnvoy(ctx, dst, url, "cafebabe", globals.DefaultPlatform, "dev")
require.EqualError(t, err, fmt.Sprintf(`expected SHA-256 sum "cafebabe", but have "%s" from %s`, tarballSHA256sum, url))
})
}
Expand All @@ -99,7 +99,7 @@ func TestUntarEnvoy(t *testing.T) {
}))
defer server.Close()

err := untarEnvoy(context.Background(), tempDir, version.TarballURL(server.URL), tarballSHA256sum, globals.CurrentPlatform, "dev")
err := untarEnvoy(context.Background(), tempDir, version.TarballURL(server.URL), tarballSHA256sum, globals.DefaultPlatform, "dev")
require.NoError(t, err)
require.FileExists(t, filepath.Join(tempDir, binEnvoy))
}
Expand All @@ -110,7 +110,7 @@ func TestInstallIfNeeded_ErrorOnIncorrectURL(t *testing.T) {

o.EnvoyVersionsURL += "/varsionz.json"

_, err := InstallIfNeeded(o.ctx, &o.GlobalOpts, globals.CurrentPlatform, version.LastKnownEnvoy)
_, err := InstallIfNeeded(o.ctx, &o.GlobalOpts, version.LastKnownEnvoy)
require.EqualError(t, err, "received 404 status code from "+o.EnvoyVersionsURL)
require.Empty(t, o.Out.(*bytes.Buffer))
}
Expand Down Expand Up @@ -141,9 +141,10 @@ func TestInstallIfNeeded_Validates(t *testing.T) {

for _, tt := range tests {
tc := tt
o.Platform = tt.p
t.Run(tc.name, func(t *testing.T) {
o.Out = new(bytes.Buffer)
_, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, tc.p, tc.v)
_, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, tc.v)
require.EqualError(t, e, tc.expectedErr)
require.Empty(t, o.Out.(*bytes.Buffer))
})
Expand All @@ -155,7 +156,7 @@ func TestInstallIfNeeded(t *testing.T) {
defer cleanup()
out := o.Out.(*bytes.Buffer)

envoyPath, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, globals.CurrentPlatform, version.LastKnownEnvoy)
envoyPath, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, version.LastKnownEnvoy)
require.NoError(t, e)
require.Equal(t, o.EnvoyPath, envoyPath)
require.FileExists(t, envoyPath)
Expand All @@ -164,7 +165,7 @@ func TestInstallIfNeeded(t *testing.T) {
versionDir := strings.Replace(envoyPath, binEnvoy, "", 1)
f, err := os.Stat(versionDir)
require.NoError(t, err)
require.Equal(t, f.ModTime().Format("2006-01-02"), string(test.FakeReleaseDate))
require.Equal(t, f.ModTime().UTC().Format("2006-01-02"), string(test.FakeReleaseDate))

require.Equal(t, fmt.Sprintln("downloading", o.tarballURL), out.String())
}
Expand All @@ -174,11 +175,13 @@ func TestInstallIfNeeded_NotFound(t *testing.T) {
defer cleanup()

t.Run("unknown version", func(t *testing.T) {
_, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, "darwin/amd64", "1.1.1")
o.Platform = "darwin/amd64"
_, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, "1.1.1")
require.EqualError(t, e, `couldn't find version "1.1.1" for platform "darwin/amd64"`)
})
t.Run("unknown platform", func(t *testing.T) {
_, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, "solaris/amd64", version.LastKnownEnvoy)
o.Platform = "solaris/amd64"
_, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, version.LastKnownEnvoy)
require.EqualError(t, e, fmt.Sprintf(`couldn't find version "%s" for platform "solaris/amd64"`, version.LastKnownEnvoy))
})
}
Expand All @@ -194,7 +197,7 @@ func TestInstallIfNeeded_AlreadyExists(t *testing.T) {
envoyStat, err := os.Stat(o.EnvoyPath)
require.NoError(t, err)

envoyPath, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, globals.CurrentPlatform, version.LastKnownEnvoy)
envoyPath, e := InstallIfNeeded(o.ctx, &o.GlobalOpts, version.LastKnownEnvoy)
require.NoError(t, e)
require.Equal(t, fmt.Sprintln(version.LastKnownEnvoy, "is already downloaded"), out.String())

Expand Down Expand Up @@ -257,6 +260,7 @@ func setupInstallTest(t *testing.T) (*installTest, func()) {
HomeDir: tempDir,
EnvoyVersionsURL: versionsServer.URL + "/envoy-versions.json",
Out: new(bytes.Buffer),
Platform: globals.DefaultPlatform,
RunOpts: globals.RunOpts{
EnvoyPath: filepath.Join(tempDir, "versions", string(version.LastKnownEnvoy), binEnvoy),
},
Expand Down
6 changes: 4 additions & 2 deletions internal/globals/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,20 @@ type GlobalOpts struct {
HomeDir string
// Out is where status messages are written. Defaults to os.Stdout
Out io.Writer
// The platform to target for the Envoy install.
Platform version.Platform
}

const (
// DefaultHomeDir is the default value for GlobalOpts.HomeDir
DefaultHomeDir = "${HOME}/.func-e"
// DefaultEnvoyVersionsURL is the default value for GlobalOpts.EnvoyVersionsURL
DefaultEnvoyVersionsURL = "https://archive.tetratelabs.io/envoy/envoy-versions.json"
// DefaultPlatform is the current platform of the host machine
DefaultPlatform = version.Platform(runtime.GOOS + "/" + runtime.GOARCH)
)

var (
// EnvoyVersionPattern is used to validate versions and is the same pattern as release-versions-schema.json.
EnvoyVersionPattern = regexp.MustCompile(`^[1-9][0-9]*\.[0-9]+\.[0-9]+(_debug)?$`)
// CurrentPlatform is the platform of the current process. This is used as a key in EnvoyVersion.Tarballs.
CurrentPlatform = version.Platform(runtime.GOOS + "/" + runtime.GOARCH)
)

0 comments on commit 94a39ab

Please sign in to comment.