Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
208 changes: 208 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Build Architecture

This document describes the Docker multi-stage build architecture used in this repository.

## Overview

The Dockerfile uses a multi-stage build approach to:
- Compile Rust code for both native and WebAssembly targets
- Build and test OpenFeature providers (JavaScript and Java)
- Package artifacts for distribution

## Build Stage Diagram

The diagram below shows all build stages and their dependencies:

```mermaid
flowchart TD
%% Base images
alpine[alpine:3.22]
node[node:20-alpine]
java[eclipse-temurin:17-jdk]

%% Rust toolchain and dependency stages
alpine --> rust-base[rust-base<br/>Rust toolchain + protoc]
rust-base --> rust-deps[rust-deps<br/>Cached Rust dependencies]

%% Native test/lint base
rust-base --> rust-test-base[rust-test-base<br/>Base for native tests/lints]
rust-deps -.->|copies cargo cache| rust-test-base

%% WASM build base
rust-base --> wasm-deps[wasm-deps<br/>WASM build environment]
rust-deps -.->|copies cargo cache| wasm-deps

%% Native Rust tests
rust-test-base --> confidence-resolver.test[confidence-resolver.test<br/>✓ Core resolver tests]
rust-test-base --> wasm-msg.test[wasm-msg.test<br/>✓ WASM msg tests]

%% Native Rust lints
rust-test-base --> confidence-resolver.lint[confidence-resolver.lint<br/>⚡ Core resolver lint]
rust-test-base --> wasm-msg.lint[wasm-msg.lint<br/>⚡ WASM msg lint]

%% WASM build and lint
wasm-deps --> wasm-rust-guest.build[wasm-rust-guest.build<br/>🔨 Build WASM resolver]
wasm-deps --> wasm-rust-guest.lint[wasm-rust-guest.lint<br/>⚡ WASM guest lint]
wasm-deps --> confidence-cloudflare-resolver.lint[confidence-cloudflare-resolver.lint<br/>⚡ Cloudflare lint]

%% WASM artifact
wasm-rust-guest.build --> wasm-rust-guest.artifact[wasm-rust-guest.artifact<br/>📦 confidence_resolver.wasm]

%% OpenFeature JS provider
node --> openfeature-provider-js-base[openfeature-provider-js-base<br/>Node deps + proto gen]
wasm-rust-guest.artifact -.->|copies WASM| openfeature-provider-js-base
openfeature-provider-js-base --> openfeature-provider-js.test[openfeature-provider-js.test<br/>✓ Provider tests]
openfeature-provider-js.test --> openfeature-provider-js.test_e2e[openfeature-provider-js.test_e2e<br/>✓ E2E tests]
openfeature-provider-js-base --> openfeature-provider-js.build[openfeature-provider-js.build<br/>🔨 TypeScript build]
openfeature-provider-js.build --> openfeature-provider-js.pack[openfeature-provider-js.pack<br/>📦 yarn pack]
openfeature-provider-js.pack --> openfeature-provider-js.artifact[openfeature-provider-js.artifact<br/>📦 package.tgz]

%% OpenFeature Java provider
java --> openfeature-provider-java-base[openfeature-provider-java-base<br/>Maven + proto gen]
wasm-rust-guest.artifact -.->|copies WASM| openfeature-provider-java-base
openfeature-provider-java-base --> openfeature-provider-java.test[openfeature-provider-java.test<br/>✓ Java provider tests]
openfeature-provider-java-base --> openfeature-provider-java.build[openfeature-provider-java.build<br/>🔨 Maven build]
openfeature-provider-java.build --> openfeature-provider-java.publish[openfeature-provider-java.publish<br/>🚀 Maven Central]

%% All stage aggregates everything
wasm-rust-guest.artifact --> all[all<br/>✅ Complete build]
confidence-resolver.test --> all
wasm-msg.test --> all
openfeature-provider-js.test --> all
openfeature-provider-js.test_e2e --> all
openfeature-provider-java.test --> all
confidence-resolver.lint --> all
wasm-msg.lint --> all
wasm-rust-guest.lint --> all
confidence-cloudflare-resolver.lint --> all
openfeature-provider-js.build --> all
openfeature-provider-java.build --> all

%% Styling
classDef baseImage fill:#e1f5ff,stroke:#0066cc
classDef buildStage fill:#fff4e1,stroke:#ff8c00
classDef testStage fill:#e8f5e9,stroke:#2e7d32
classDef lintStage fill:#fff3e0,stroke:#f57c00
classDef artifact fill:#f3e5f5,stroke:#7b1fa2
classDef publish fill:#ffebee,stroke:#c62828
classDef final fill:#c8e6c9,stroke:#388e3c

class alpine,node,java baseImage
class rust-base,rust-deps,rust-test-base,wasm-deps,openfeature-provider-js-base,openfeature-provider-java-base baseImage
class wasm-rust-guest.build,openfeature-provider-js.build,openfeature-provider-java.build buildStage
class confidence-resolver.test,wasm-msg.test,openfeature-provider-js.test,openfeature-provider-js.test_e2e,openfeature-provider-java.test testStage
class confidence-resolver.lint,wasm-msg.lint,wasm-rust-guest.lint,confidence-cloudflare-resolver.lint lintStage
class wasm-rust-guest.artifact,openfeature-provider-js.pack,openfeature-provider-js.artifact artifact
class openfeature-provider-java.publish publish
class all final
```

**Legend:**
- 🔨 Build stages compile code
- ✓ Test stages run unit/integration tests
- ⚡ Lint stages run code quality checks
- 📦 Artifact stages extract build outputs
- 🚀 Publish stages deploy to registries
- ✅ Final `all` stage aggregates everything

## Key Features

### Dependency Caching
Rust dependencies are compiled once in the `rust-deps` stage and reused across all subsequent builds. This significantly speeds up incremental builds.

### Parallel Execution
Test and lint stages are independent and can run concurrently, reducing total build time.

### WASM Artifact Sharing
The core `confidence_resolver.wasm` is built once in `wasm-rust-guest.build` and shared across:
- OpenFeature JavaScript provider
- OpenFeature Java provider

### Targeted Builds
You can build specific components using Docker's `--target` flag:

```bash
# Build only the WASM artifact
docker build --target=wasm-rust-guest.artifact .

# Build and extract the npm package
docker build --target=openfeature-provider-js.artifact .

# Run only JavaScript provider tests
docker build --target=openfeature-provider-js.test .

# Build everything (default)
docker build .
```

## Stage Descriptions

### Base Stages

- **rust-base** (FROM alpine:3.22): Installs Rust toolchain via rustup, protoc, and build dependencies
- **rust-deps** (FROM rust-base): Compiles all Rust workspace dependencies (cached layer for faster rebuilds)
- **rust-test-base** (FROM rust-base): Copies dependency cache and source code for native testing/linting
- **wasm-deps** (FROM rust-base): Copies dependency cache and source code for WASM builds
- **openfeature-provider-js-base** (FROM node:20-alpine): Node.js environment with Yarn, dependencies, and proto generation
- **openfeature-provider-java-base** (FROM eclipse-temurin:17-jdk): Java environment with Maven and proto files

### Test Stages

- **confidence-resolver.test** (FROM rust-test-base): Unit tests for core resolver
- **wasm-msg.test** (FROM rust-test-base): Tests for WASM messaging layer
- **openfeature-provider-js.test** (FROM openfeature-provider-js-base): Unit tests for JavaScript provider
- **openfeature-provider-js.test_e2e** (FROM openfeature-provider-js.test): End-to-end tests (requires credentials via Docker secret)
- **openfeature-provider-java.test** (FROM openfeature-provider-java-base): Tests for Java provider

### Lint Stages

- **confidence-resolver.lint** (FROM rust-test-base): Clippy checks for core resolver
- **wasm-msg.lint** (FROM rust-test-base): Clippy checks for WASM messaging
- **wasm-rust-guest.lint** (FROM wasm-deps): Clippy checks for WASM guest
- **confidence-cloudflare-resolver.lint** (FROM wasm-deps): Clippy checks for Cloudflare resolver

### Build Stages

- **wasm-rust-guest.build** (FROM wasm-deps): Compiles Rust resolver to WebAssembly (wasm32-unknown-unknown target)
- **openfeature-provider-js.build** (FROM openfeature-provider-js-base): Compiles TypeScript to JavaScript
- **openfeature-provider-java.build** (FROM openfeature-provider-java-base): Builds Java provider with Maven

### Artifact Stages

- **wasm-rust-guest.artifact** (FROM scratch): Extracts `confidence_resolver.wasm` (rust_guest.wasm → confidence_resolver.wasm)
- **openfeature-provider-js.pack** (FROM openfeature-provider-js.build): Creates npm package tarball via `yarn pack`
- **openfeature-provider-js.artifact** (FROM scratch): Extracts package.tgz for distribution

### Publish Stages

- **openfeature-provider-java.publish** (FROM openfeature-provider-java.build): Publishes Java provider to Maven Central (requires GPG and Maven secrets)

### Aggregation Stage

- **all** (FROM scratch): Default stage that ensures all tests, lints, and builds complete successfully by copying marker files

## CI/CD Integration

The build stages are used in GitHub Actions workflows:

- **release-please.yml**: Publishes packages when releases are created
- Uses `openfeature-provider-js.artifact` to extract npm package
- Uses `openfeature-provider-java.publish` to deploy to Maven Central

## Docker Build Cache

The repository uses Docker layer caching to speed up builds in CI:

```yaml
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}/cache:main
```

This allows GitHub Actions to reuse layers from previous builds.

## Dependency Flow

The `rust-deps` stage builds dummy source files to compile all workspace dependencies, creating a cached layer. This cache is then copied into:
- `rust-test-base` (for native tests and lints)
- `wasm-deps` (for WASM builds)

This approach ensures dependencies are only compiled once, even when building multiple targets.
165 changes: 0 additions & 165 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -198,165 +198,6 @@ FROM wasm-deps AS confidence-cloudflare-resolver.lint
WORKDIR /workspace/confidence-cloudflare-resolver
RUN make lint

# ==============================================================================
# Node.js Host - Run Node.js host example
# ==============================================================================
FROM node:20-alpine AS node-host-base

# Install protoc for proto generation
RUN apk add --no-cache protobuf-dev protoc make

WORKDIR /app

# Enable Corepack for Yarn
RUN corepack enable

# Copy package files for dependency caching
COPY wasm/node-host/package.json wasm/node-host/yarn.lock wasm/node-host/.yarnrc.yml ./
COPY wasm/node-host/Makefile ./

# Copy proto files for generation
COPY wasm/proto ../proto/

# Build using Makefile (installs deps + generates protos)
ENV IN_DOCKER_BUILD=1
RUN make build

# Copy source code
COPY wasm/node-host/src ./src/
COPY wasm/node-host/tsconfig.json ./

# Copy WASM module from wasm-rust-guest.artifact
COPY --from=wasm-rust-guest.artifact /confidence_resolver.wasm ../confidence_resolver.wasm

# Copy resolver state
COPY wasm/resolver_state.pb ../resolver_state.pb

# ==============================================================================
# Test Node.js Host (integration test)
# ==============================================================================
FROM node-host-base AS node-host.test
RUN make run

# ==============================================================================
# Java Host - Run Java host example
# ==============================================================================
FROM eclipse-temurin:21-alpine AS java-host-base

# Install Maven and protobuf
RUN apk add --no-cache maven protobuf-dev protoc make

WORKDIR /app

# Copy pom.xml for dependency caching
COPY wasm/java-host/pom.xml ./
COPY wasm/java-host/Makefile ./

# Download dependencies (this layer will be cached)
RUN mvn dependency:go-offline -q || true

# Copy proto files
COPY wasm/proto ../proto/

# Copy source code
COPY wasm/java-host/src ./src/

# Build using Makefile (compiles proto + builds JAR)
ENV IN_DOCKER_BUILD=1
RUN make build

# Copy WASM module from wasm-rust-guest.artifact
COPY --from=wasm-rust-guest.artifact /confidence_resolver.wasm ../confidence_resolver.wasm

# Copy resolver state
COPY wasm/resolver_state.pb ../resolver_state.pb

# ==============================================================================
# Test Java Host (integration test)
# ==============================================================================
FROM java-host-base AS java-host.test
RUN make run

# ==============================================================================
# Go Host - Run Go host example
# ==============================================================================
FROM golang:1.23-alpine AS go-host-base

# Install protobuf and protoc-gen-go
RUN apk add --no-cache protobuf-dev protoc bash make git

WORKDIR /app

# Copy go.mod for dependency caching
COPY wasm/go-host/go.mod wasm/go-host/go.sum ./
COPY wasm/go-host/Makefile ./

# Download Go dependencies (this layer will be cached)
RUN go mod download

# Install protoc-gen-go (pin version for stability)
RUN go install google.golang.org/protobuf/cmd/[email protected]

# Copy proto files
COPY wasm/proto ../proto/

# Copy source code
COPY wasm/go-host/*.go wasm/go-host/*.sh ./

# Build using Makefile (generates proto + builds)
ENV IN_DOCKER_BUILD=1
RUN make build

# Copy WASM module
COPY --from=wasm-rust-guest.artifact /confidence_resolver.wasm ../confidence_resolver.wasm

# Copy resolver state
COPY wasm/resolver_state.pb ../resolver_state.pb

# ==============================================================================
# Test Go Host (integration test)
# ==============================================================================
FROM go-host-base AS go-host.test
RUN make run

# ==============================================================================
# Python Host - Run Python host example
# ==============================================================================
FROM python:3.11-slim AS python-host-base

# Install protobuf and dependencies (libprotobuf-dev includes google proto files)
RUN apt-get update && \
apt-get install -y --no-install-recommends protobuf-compiler libprotobuf-dev make && \
rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy Makefile and proto generation script
COPY wasm/python-host/Makefile ./
COPY wasm/python-host/generate_proto.py ./

# Copy proto files
COPY wasm/proto ../proto/

# Build using Makefile (creates venv + installs deps + generates proto)
ENV IN_DOCKER_BUILD=1
RUN make build

# Copy source code
COPY wasm/python-host/*.py ./

# Copy WASM module
COPY --from=wasm-rust-guest.artifact /confidence_resolver.wasm ../confidence_resolver.wasm

# Copy resolver state
COPY wasm/resolver_state.pb ../resolver_state.pb

# ==============================================================================
# Test Python Host (integration test)
# ==============================================================================
FROM python-host-base AS python-host.test
RUN make run

# ==============================================================================
# OpenFeature Provider (TypeScript) - Build and test
# ==============================================================================
Expand Down Expand Up @@ -514,12 +355,6 @@ COPY --from=openfeature-provider-js.test /app/package.json /markers/test-openfea
COPY --from=openfeature-provider-js.test_e2e /app/package.json /markers/test-openfeature-js-e2e
COPY --from=openfeature-provider-java.test /app/pom.xml /markers/test-openfeature-java

# Force integration test stages to run (host examples)
COPY --from=node-host.test /app/package.json /markers/integration-node
COPY --from=java-host.test /app/pom.xml /markers/integration-java
COPY --from=go-host.test /app/go.mod /markers/integration-go
COPY --from=python-host.test /app/Makefile /markers/integration-python

# Force lint stages to run by copying marker files
COPY --from=confidence-resolver.lint /workspace/Cargo.toml /markers/lint-resolver
COPY --from=wasm-msg.lint /workspace/Cargo.toml /markers/lint-wasm-msg
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ make run-go
make run-python
```

See [BUILD.md](BUILD.md) for detailed information about the Docker multi-stage build architecture.

## Running the example hosts

There are host implementations for different languages in the `wasm` folder.
Expand Down
Loading