Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a0ee767
Add WASI integration architecture design document
cardil Feb 17, 2026
c29f420
docs: fix inconsistencies in WASI integration design
cardil Feb 17, 2026
a151f70
Merge remote-tracking branch 'upstream/main' into feature/wasi-integr…
cardil Feb 17, 2026
c60857d
Add core WASM package for Phase 1 WASI integration
cardil Feb 17, 2026
6e91914
feat: builder/deployer registry with WASI inference and compatibility…
cardil Feb 24, 2026
2fa4f5a
Add rust-wasi and go-wasi HTTP function templates
cardil Mar 3, 2026
67d4efd
feat(wasm): add WASM builder/deployer for WASI runtimes
cardil Mar 4, 2026
6c9a5d8
wasm: wire remover/describer/lister; bump knative-serving-wasm; fix g…
cardil Mar 5, 2026
d1de334
wasm: refactor tests and cluster setup
cardil Mar 5, 2026
e16bdce
wasm: e2e readiness, in-cluster registry, deployer polling, PID fix
cardil Mar 30, 2026
35ef22b
fix(go-wasi): export wasi:http/incoming-handler via WIT bindings
cardil Apr 1, 2026
7de29ad
fix: hasGoGenerateDirective double-close panic + sem deadlock
cardil Apr 1, 2026
94d3e70
fix(go-wasi): end-to-end build producing wasi:http/incoming-handler@0…
cardil Apr 1, 2026
db06b73
fix(hack): add KnativeServingWasm to component-versions template
cardil Apr 1, 2026
133b2ab
Fix WIT transitive dep conflicts with forgiving provider mode
cardil Apr 1, 2026
d0ee20b
fix: detect podman-docker in CONTAINER_ENGINE resolution
cardil Apr 1, 2026
8a4ab89
chore: regenerate embedded FS and docs
cardil Apr 1, 2026
b34d8b0
expose run.args in func.yaml, map to WasmModule spec.args
cardil Apr 1, 2026
1521ff1
Merge upstream/main into feature/wasi-integration
cardil Apr 1, 2026
d6d6cfe
fix: update docs codegen and remove insensitive word in test
cardil Apr 1, 2026
50d8950
fix: sort builder/deployer names for deterministic docs
cardil Apr 1, 2026
bd55961
ci: add WASI e2e test job and make target
cardil Apr 1, 2026
4c4dd6b
Merge remote-tracking branch 'upstream/main' into feature/wasi-integr…
cardil Apr 2, 2026
10cf58e
fix: flaky TestSaveVersions_SortedOutput due to map iteration order
cardil Apr 2, 2026
cecff33
fix: treat RevisionMissing as transient during WASM deploy
cardil Apr 2, 2026
c7418a6
Merge remote-tracking branch 'upstream/main' into feature/wasi-integr…
cardil Apr 7, 2026
1ef2bdf
fix: avoid port 8080 conflict in Config CI tests and GitLab setup
cardil Apr 7, 2026
b65a22d
fix: adapt tests for non-privileged ingress ports (8080/8443)
cardil Apr 7, 2026
8b856e6
Merge remote-tracking branch 'upstream/main' into feature/wasi-integr…
cardil Apr 8, 2026
49e6cca
fix: correct variable redeclaration in builders_int_test.go
cardil Apr 8, 2026
f74b730
Merge remote-tracking branch 'upstream/main' into feature/wasi-integr…
cardil Apr 9, 2026
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
69 changes: 69 additions & 0 deletions .github/workflows/functions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ jobs:
timeout-minutes: 120
env:
FUNC_CLUSTER_RETRIES: 5
FUNC_E2E_HTTP_PORT: "8080"
FUNC_INT_TEKTON_ENABLED: true
FUNC_INT_GITLAB_ENABLED: true
FUNC_INT_GITLAB_HOSTNAME: gitlab.localtest.me
Expand Down Expand Up @@ -450,6 +451,73 @@ jobs:
path: ./cluster_log.txt
retention-days: 7

# ---------------
# E2E WASI TESTS
# ---------------
test-e2e-wasi:
name: E2E - WASI (rust-wasi, go-wasi)
needs: precheck
runs-on: ubuntu-latest
timeout-minutes: 120
env:
FUNC_CLUSTER_RETRIES: 5
FUNC_E2E_CLEAN: false
FUNC_E2E_VERBOSE: true
steps:
- uses: actions/checkout@v4
- uses: knative/actions/setup-go@main
- uses: endersonmenezes/free-disk-space@v3
with:
remove_android: true
remove_dotnet: true
remove_haskell: true
remove_swap: true
rm_cmd: "rmz"

- name: Setup Rust with wasm32-wasip2 target
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: wasm32-wasip2

- name: Setup TinyGo
run: |
TINYGO_VERSION=0.36.0
wget -q "https://github.com/tinygo-org/tinygo/releases/download/v${TINYGO_VERSION}/tinygo_${TINYGO_VERSION}_amd64.deb"
sudo dpkg -i "tinygo_${TINYGO_VERSION}_amd64.deb"
tinygo version

- name: Install Binaries
run: ./hack/binaries.sh
- name: Allocate Cluster
run: ./hack/cluster.sh
- name: Start Local Registry
run: ./hack/registry.sh
- name: Prepare Images
run: ./hack/images.sh

- name: Run WASI E2E Tests
run: make test-e2e-wasi

- uses: codecov/codecov-action@v5
with:
files: ./coverage.txt
flags: e2e-wasi
fail_ci_if_error: true
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}

# Preserve Cluster Logs
- name: Dump Cluster Logs
if: always()
run: ./hack/dump-logs.sh cluster_log.txt
- name: Archive Cluster Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: cluster-logs-e2e-wasi
path: ./cluster_log.txt
retention-days: 7

# Build and Publish
build:
name: Build Release
Expand All @@ -461,6 +529,7 @@ jobs:
- test-e2e-podman
- test-e2e-runtimes
- test-e2e-config-ci
- test-e2e-wasi
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 30
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ test-e2e-matrix: func-instrumented-bin ## Basic E2E tests (includes core, metada
FUNC_E2E_MATRIX=true go test -tags e2e -timeout 120m ./e2e -v -run TestMatrix_
go tool covdata textfmt -i=$${FUNC_E2E_GOCOVERDIR:-.coverage} -o coverage.txt

.PHONY: test-e2e-wasi
test-e2e-wasi: func-instrumented-bin ## WASI E2E tests (rust-wasi, go-wasi runtimes)
go test -tags e2e -timeout 120m ./e2e -v -run TestWasm_
go tool covdata textfmt -i=$${FUNC_E2E_GOCOVERDIR:-.coverage} -o coverage.txt

.PHONY: test-e2e-config-ci
test-e2e-config-ci: func-instrumented-bin ## CI tests for generated GitHub Workflows
# Runtime and other options can be configured using the FUNC_E2E_* environment variables. see e2e_test.go
Expand Down
154 changes: 91 additions & 63 deletions cmd/build.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"errors"
"fmt"
"io"
Expand All @@ -11,12 +12,8 @@ import (
"github.com/ory/viper"
"github.com/spf13/cobra"
"knative.dev/func/pkg/builders"
"knative.dev/func/pkg/buildpacks"
"knative.dev/func/pkg/config"
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci"
"knative.dev/func/pkg/s2i"
)

func NewBuildCmd(newClient ClientFactory) *cobra.Command {
Expand Down Expand Up @@ -165,14 +162,19 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
if !f.Initialized() {
return NewErrNotInitializedFromPath(f.Root, "build")
}
// Track if builder was explicitly set via --builder flag or already persisted
// in func.yaml. Must be checked before Configure() overwrites f.Build.Builder
// with the static default from config.
originalBuilder := f.Build.Builder // value from func.yaml before Configure()
cfg.BuilderExplicit = cmd.Flags().Changed("builder") || f.Build.Builder != ""

// Warn if registry changed but registryInsecure is still true
warnRegistryInsecureChange(os.Stderr, cfg.Registry, f)

f = cfg.Configure(f) // Returns an f updated with values from the config (flags, envs, etc)

// Client
clientOptions, err := cfg.clientOptions()
clientOptions, err := cfg.clientOptions(f.Runtime)
if err != nil {
return
}
Expand All @@ -197,6 +199,14 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
return
}
}
// If builder was inferred (not set via --builder or func.yaml), do not persist
// the inferred value. On the next build inference will be repeated, and the
// static default (e.g. "pack") won't accidentally lock a WASI function.
if !cfg.BuilderExplicit {
if inferred := newRegistry().InferBuilder(f.Runtime); inferred != "" {
f.Build.Builder = originalBuilder // restore pre-Configure value (empty → omitempty)
}
}
if err = f.Write(); err != nil {
return
}
Expand All @@ -218,6 +228,11 @@ type buildConfig struct {
// Globals (builder, confirm, registry, verbose)
config.Global

// BuilderExplicit is true when the builder was set via --builder flag
// or was already persisted in func.yaml. When false, runtime-based
// inference is used (e.g. WASI runtimes → "wasm" builder).
BuilderExplicit bool

// BuilderImage is the image (name or mapping) to use for building. Usually
// set automatically.
BuilderImage string
Expand Down Expand Up @@ -304,7 +319,7 @@ func (c buildConfig) Configure(f fn.Function) fn.Function {
func (c buildConfig) Prompt() (buildConfig, error) {
// If there is no registry nor explicit image name defined, the
// Registry prompt is shown whether or not we are in confirm mode.
// Otherwise, it is only showin if in confirm mode
// Otherwise, it is only shown if in confirm mode
// NOTE: the default in this latter situation will ignore the current function
// value and will always use the value from the config (flag or env variable).
// This is not strictly correct and will be fixed when Global Config: Function
Expand Down Expand Up @@ -342,10 +357,7 @@ func (c buildConfig) Prompt() (buildConfig, error) {
}

// Remainder of prompts are optional and only shown if in --confirm mode
// Image Name Override
// Calculate a better image name message which shows the value of the final
// image name as it will be calculated if an explicit image name is not used.

r := newRegistry()
qs := []*survey.Question{
{
Name: "image",
Expand All @@ -364,7 +376,7 @@ func (c buildConfig) Prompt() (buildConfig, error) {
Name: "builder",
Prompt: &survey.Select{
Message: "Select builder:",
Options: []string{"pack", "s2i", "host"},
Options: r.ListBuilders(),
Default: c.Builder,
},
},
Expand Down Expand Up @@ -403,7 +415,7 @@ func (c buildConfig) Prompt() (buildConfig, error) {

// Validate the config passes an initial consistency check
func (c buildConfig) Validate(cmd *cobra.Command) (err error) {
// Builder value must refer to a known builder short name
// Builder value must refer to a registered builder
if err = ValidateBuilder(c.Builder); err != nil {
return
}
Expand All @@ -429,62 +441,58 @@ func (c buildConfig) Validate(cmd *cobra.Command) (err error) {

// clientOptions returns options suitable for instantiating a client based on
// the current state of the build config object.
// This will be unnecessary and refactored away when the host-based OCI
// builder and pusher are the default implementations and the Pack and S2I
// constructors simplified.
//
// TODO: Platform is currently only used by the S2I builder. This should be
// a multi-valued argument which passes through to the "host" builder (which
// supports multi-arch/platform images), and throw an error if either trying
// to specify a platform for buildpacks, or trying to specify more than one
// for S2I.
//
// TODO: As a further optimization, it might be ideal to only build the
// image necessary for the target cluster, since the end product of a function
// deployment is not the container, but rather the running service.
func (c buildConfig) clientOptions() ([]fn.Option, error) {
// runtime is the function's runtime string (e.g. "go", "rust-wasi") used to
// infer the builder when the user has not explicitly specified one via --builder.
func (c buildConfig) clientOptions(runtime string) ([]fn.Option, error) {
o := []fn.Option{
fn.WithRegistry(c.Registry),
fn.WithRegistryInsecure(c.RegistryInsecure),
}

t := newTransport(c.RegistryInsecure)
creds := newCredentialsProvider(config.Dir(), t, c.RegistryAuthfile)

switch c.Builder {
case builders.Host:
o = append(o,
fn.WithScaffolder(oci.NewScaffolder(c.Verbose)),
fn.WithBuilder(oci.NewBuilder(builders.Host, c.Verbose)),
fn.WithPusher(oci.NewPusher(c.RegistryInsecure, false, c.Verbose,
oci.WithTransport(newTransport(c.RegistryInsecure)),
oci.WithCredentialsProvider(creds),
oci.WithVerbose(c.Verbose))),
)
case builders.Pack:
o = append(o,
fn.WithScaffolder(buildpacks.NewScaffolder(c.Verbose)),
fn.WithBuilder(buildpacks.NewBuilder(
buildpacks.WithName(builders.Pack),
buildpacks.WithTimestamp(c.WithTimestamp),
buildpacks.WithVerbose(c.Verbose))),
fn.WithPusher(docker.NewPusher(
docker.WithCredentialsProvider(creds),
docker.WithTransport(t),
docker.WithVerbose(c.Verbose))))
case builders.S2I:
o = append(o,
fn.WithScaffolder(s2i.NewScaffolder(c.Verbose)),
fn.WithBuilder(s2i.NewBuilder(
s2i.WithName(builders.S2I),
s2i.WithVerbose(c.Verbose))),
fn.WithPusher(docker.NewPusher(
docker.WithCredentialsProvider(creds),
docker.WithTransport(t),
docker.WithVerbose(c.Verbose))))
default:
return o, builders.ErrUnknownBuilder{Name: c.Builder, Known: KnownBuilders()}
credsProvider := newCredentialsProvider(config.Dir(), t, c.RegistryAuthfile)

// Convert oci.CredentialsProvider to fn.CredentialsCallback so BuilderConfig
// remains stdlib-only (no import of pkg/oci).
credsCB := func(ctx context.Context, image string) (string, string, error) {
creds, err := credsProvider(ctx, image)
return creds.Username, creds.Password, err
}

// Resolve the builder name:
// explicit (--builder flag or func.yaml) > inferred from runtime > static default
r := newRegistry()
builderName := c.Builder
if !c.BuilderExplicit {
// No explicit choice — try runtime-based inference.
// For traditional runtimes InferBuilder returns "" (keeps static default).
// For WASI runtimes it returns "wasm".
if inferred := r.InferBuilder(runtime); inferred != "" {
builderName = inferred
}
}

reg, ok := r.GetBuilder(builderName)
if !ok {
return o, fmt.Errorf("unknown builder %q; registered builders: %v", builderName, r.ListBuilders())
}

// Validate that the resolved builder supports the function's runtime.
// This guards against e.g. --builder=pack used with a wasi runtime.
if runtime != "" {
if err := r.ValidateBuilderCompatibility(runtime, builderName); err != nil {
return o, err
}
}

builderOpts := reg.Factory(fn.BuilderConfig{
Verbose: c.Verbose,
Transport: t,
RegistryInsecure: c.RegistryInsecure,
Credentials: credsCB,
WithTimestamp: c.WithTimestamp,
})
o = append(o, builderOpts...)
return o, nil
}

Expand All @@ -496,15 +504,35 @@ func (c buildConfig) buildOptions() (oo []fn.BuildOption, err error) {
//
// TODO: upgrade --platform to a multi-value field. The individual builder
// implementations are responsible for bubbling an error if they do
// not support this. Pack supports none, S2I supports one, host builder
// not support this. Pack supports none, S2I supports one, host builder
// supports multi.
if c.Platform != "" {
parts := strings.Split(c.Platform, "/")
if len(parts) != 2 {
return oo, fmt.Errorf("the value for --patform must be in the form [OS]/[Architecture]. eg \"linux/amd64\"")
return oo, fmt.Errorf("the value for --platform must be in the form [OS]/[Architecture]. eg \"linux/amd64\"")
}
oo = append(oo, fn.BuildWithPlatforms([]fn.Platform{{OS: parts[0], Architecture: parts[1]}}))
}

return
}

// ValidateBuilder ensures that the given builder name is registered.
func ValidateBuilder(name string) error {
r := newRegistry()
if _, ok := r.GetBuilder(name); ok {
return nil
}
return builders.ErrUnknownBuilder{Name: name, Known: builders.Known(r.ListBuilders())}
}

// KnownBuilders returns all registered builder names as a builders.Known slice
// (for use in flag help text).
func KnownBuilders() builders.Known {
return builders.Known(newRegistry().ListBuilders())
}

// KnownDeployers returns all registered deployer names as a slice.
func KnownDeployers() []string {
return newRegistry().ListDeployers()
}
Loading
Loading