Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ main_sources := $(wildcard $(filter-out %_test.go $(all_testdata) $(all_testuti
main_packages := $(sort $(foreach f,$(dir $(main_sources)),$(if $(findstring ./,$(f)),./,./$(f))))

build/func-e_%/func-e: $(main_sources)
$(call go-build,$@,$<)
$(call go-build,$@)

dist/func-e_$(VERSION)_%.tar.gz: build/func-e_%/func-e
@printf "$(ansi_format_dark)" tar.gz "tarring $@"
Expand Down Expand Up @@ -216,7 +216,7 @@ define go-build
@# $(go:go=) removes the trailing 'go', so we can insert cross-build variables
@$(go:go=) CGO_ENABLED=0 GOOS=$(call go-os,$1) GOARCH=$(call go-arch,$1) go build \
-ldflags "-s -w -X main.version=$(VERSION)" \
-o $1 $2
-o $1 ./cmd/func-e
@printf "$(ansi_format_bright)" build "ok"
endef

Expand Down
131 changes: 0 additions & 131 deletions api/func-e.go

This file was deleted.

77 changes: 77 additions & 0 deletions api/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2025 Tetrate
// SPDX-License-Identifier: Apache-2.0

// Package api allows Go projects to use func-e as a library, decoupled from how
// the func-e binary reads environment variables or CLI args.
package api

import (
"context"
"io"

"github.com/tetratelabs/func-e/internal/opts"
)

// HomeDir is an absolute path which most importantly contains "versions"
// installed from EnvoyVersionsURL. Defaults to "${HOME}/.func-e"
func HomeDir(homeDir string) RunOption {
return func(o *opts.RunOpts) {
o.HomeDir = homeDir
}
}

// EnvoyVersionsURL is the path to the envoy-versions.json.
// Defaults to "https://archive.tetratelabs.io/envoy/envoy-versions.json"
func EnvoyVersionsURL(envoyVersionsURL string) RunOption {
return func(o *opts.RunOpts) {
o.EnvoyVersionsURL = envoyVersionsURL
}
}

// EnvoyVersion overrides the version of Envoy to run. Defaults to the
// contents of "$HomeDir/versions/version".
//
// When that file is missing, it is generated from ".latestVersion" from the
// EnvoyVersionsURL. Its value can be in full version major.minor.patch format,
// e.g. 1.18.1 or without patch component, major.minor, e.g. 1.18.
func EnvoyVersion(envoyVersion string) RunOption {
return func(o *opts.RunOpts) {
o.EnvoyVersion = envoyVersion
}
}

// Out is where status messages are written. Defaults to os.Stdout
func Out(out io.Writer) RunOption {
return func(o *opts.RunOpts) {
o.Out = out
}
}

// EnvoyOut sets the writer for Envoy stdout
func EnvoyOut(w io.Writer) RunOption {
return func(o *opts.RunOpts) {
o.EnvoyOut = w
}
}

// EnvoyErr sets the writer for Envoy stderr
func EnvoyErr(w io.Writer) RunOption {
return func(o *opts.RunOpts) {
o.EnvoyErr = w
}
}

// RunOption is a configuration for RunFunc.
//
// Note: None of these default to values read from OS environment variables.
// If you wish to introduce such behavior, populate them in calling code.
type RunOption func(*opts.RunOpts)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this uses an internal type which is a trick to effectively hide the exported fields


// RunFunc downloads Envoy and runs it as a process with the arguments
// passed to it. Use api.RunOption for configuration options.
//
// On success, this blocks and returns nil when either `ctx` is done, or the
// process exits with status zero.
//
// The default implementation of RunFunc is func_e.Run.
type RunFunc func(ctx context.Context, args []string, options ...RunOption) error
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This directory holds the end-to-end tests for `func-e`.

By default, end-to-end (e2e) tests verify a `func-e` binary built from [main.go](../main.go).
By default, end-to-end (e2e) tests verify a `func-e` binary built from [main.go](../cmd/func-e/main.go).

## Using native go commands:
End-to-end tests default to look for `func-e`, in the project root (current directory).
Expand Down
2 changes: 1 addition & 1 deletion e2e/func-e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func readOrBuildFuncEBin() error {
return fmt.Errorf("failed to create build directory %s: %w", buildDir, err)
}
var err error
if funcEBin, err = build.GoBuild(filepath.Join(projectRoot, "main.go"), buildDir); err != nil {
if funcEBin, err = build.GoBuild(filepath.Join(projectRoot, "cmd/func-e/main.go"), buildDir); err != nil {
return err
}
}
Expand Down
20 changes: 13 additions & 7 deletions internal/envoy/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,25 @@ func (r *Runtime) Run(ctx context.Context, args []string) error {
// Process stderr in a goroutine
go r.processStderr(ctx, stderrPipe, errCh)

// Wait for the process to exit
waitErr := cmd.Wait()

// After process exit, check for any stderr processing error
// Wait for the process, and any stderr processing, to complete
exitErr := cmd.Wait()
stderrErr := <-errCh

// First, check for stderr errors (e.g. from startup hook)
if stderrErr != nil {
return stderrErr
}

if waitErr == nil || errors.Is(ctx.Err(), context.Canceled) {
return nil // don't treat context cancel (graceful shutdown) as an error
// Next, handle process exit errors
if exitErr != nil {
// Only ignore exit errors on cancellation if there was no stderr error
if errors.Is(ctx.Err(), context.Canceled) {
return nil
}
return exitErr
}
return waitErr

return nil // Clean exit
}

// processStderr scans stderr output and triggers the startup hook when Envoy is ready.
Expand Down
19 changes: 19 additions & 0 deletions internal/opts/opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright func-e contributors
// SPDX-License-Identifier: Apache-2.0

// Package opts holds shared configuration types for func-e options.
// This is internal and not intended for direct use by external packages.
package opts

import "io"

// RunOpts holds the configuration set by RunOptions.
type RunOpts struct {
HomeDir string
EnvoyVersion string
EnvoyVersionsURL string
Out io.Writer
EnvoyOut io.Writer
EnvoyErr io.Writer
EnvoyPath string // Internal: path to the Envoy binary (for tests).
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright func-e contributors
// SPDX-License-Identifier: Apache-2.0

package api
package run

import (
"context"
Expand Down
25 changes: 9 additions & 16 deletions api/func-e_test.go → internal/run/func-e_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright func-e contributors
// SPDX-License-Identifier: Apache-2.0

package api
package run

import (
"context"
Expand All @@ -12,28 +12,21 @@ import (
"strconv"
"testing"

"github.com/tetratelabs/func-e/internal/api"
"github.com/tetratelabs/func-e/api"
internalapi "github.com/tetratelabs/func-e/internal/api"
"github.com/tetratelabs/func-e/internal/globals"
"github.com/tetratelabs/func-e/internal/test"
"github.com/tetratelabs/func-e/internal/test/e2e"
"github.com/tetratelabs/func-e/internal/version"
)

// fakeFuncEFactory implements runtest.FuncEFactory for API tests using fake envoy
type fakeFuncEFactory struct{}

func (fakeFuncEFactory) New(ctx context.Context, t *testing.T, stdout, stderr io.Writer) (e2e.FuncE, error) {
// Start and scope a fake version server to the test calling New
versionsServer := test.RequireEnvoyVersionsTestServer(t, version.LastKnownEnvoy)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

versionsServer is ignored when you supply the internal-only envoyPath, so this makes the tests run slightly quicker

t.Cleanup(func() { versionsServer.Close() })

o, err := initOpts(ctx, HomeDir(t.TempDir()),
envoyPath(fakeEnvoyBin),
EnvoyVersionsURL(versionsServer.URL+"/envoy-versions.json"),
EnvoyVersion(string(version.LastKnownEnvoy)),
Out(stdout),
EnvoyOut(stdout),
EnvoyErr(stderr))
o, err := initOpts(ctx, api.HomeDir(t.TempDir()),
EnvoyPath(fakeEnvoyBin),
api.Out(stdout),
api.EnvoyOut(stdout),
api.EnvoyErr(stderr))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -76,5 +69,5 @@ func (f *fakeFuncE) Run(ctx context.Context, args []string) error {
// Since we aren't launching a real process, we proxy interrupt with context cancellation.
ctx, cancel := context.WithCancel(ctx)
f.cancelFunc = cancel
return api.Run(ctx, f.o, args)
return internalapi.Run(ctx, f.o, args)
}
2 changes: 1 addition & 1 deletion api/main_test.go → internal/run/main_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright func-e contributors
// SPDX-License-Identifier: Apache-2.0

package api
package run

import (
"fmt"
Expand Down
Loading
Loading