diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..ed2bd67 --- /dev/null +++ b/BUILD.md @@ -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
Rust toolchain + protoc] + rust-base --> rust-deps[rust-deps
Cached Rust dependencies] + + %% Native test/lint base + rust-base --> rust-test-base[rust-test-base
Base for native tests/lints] + rust-deps -.->|copies cargo cache| rust-test-base + + %% WASM build base + rust-base --> wasm-deps[wasm-deps
WASM build environment] + rust-deps -.->|copies cargo cache| wasm-deps + + %% Native Rust tests + rust-test-base --> confidence-resolver.test[confidence-resolver.test
✓ Core resolver tests] + rust-test-base --> wasm-msg.test[wasm-msg.test
✓ WASM msg tests] + + %% Native Rust lints + rust-test-base --> confidence-resolver.lint[confidence-resolver.lint
⚡ Core resolver lint] + rust-test-base --> wasm-msg.lint[wasm-msg.lint
⚡ WASM msg lint] + + %% WASM build and lint + wasm-deps --> wasm-rust-guest.build[wasm-rust-guest.build
🔨 Build WASM resolver] + wasm-deps --> wasm-rust-guest.lint[wasm-rust-guest.lint
⚡ WASM guest lint] + wasm-deps --> confidence-cloudflare-resolver.lint[confidence-cloudflare-resolver.lint
⚡ Cloudflare lint] + + %% WASM artifact + wasm-rust-guest.build --> wasm-rust-guest.artifact[wasm-rust-guest.artifact
📦 confidence_resolver.wasm] + + %% OpenFeature JS provider + node --> openfeature-provider-js-base[openfeature-provider-js-base
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
✓ Provider tests] + openfeature-provider-js.test --> openfeature-provider-js.test_e2e[openfeature-provider-js.test_e2e
✓ E2E tests] + openfeature-provider-js-base --> openfeature-provider-js.build[openfeature-provider-js.build
🔨 TypeScript build] + openfeature-provider-js.build --> openfeature-provider-js.pack[openfeature-provider-js.pack
📦 yarn pack] + openfeature-provider-js.pack --> openfeature-provider-js.artifact[openfeature-provider-js.artifact
📦 package.tgz] + + %% OpenFeature Java provider + java --> openfeature-provider-java-base[openfeature-provider-java-base
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
✓ Java provider tests] + openfeature-provider-java-base --> openfeature-provider-java.build[openfeature-provider-java.build
🔨 Maven build] + openfeature-provider-java.build --> openfeature-provider-java.publish[openfeature-provider-java.publish
🚀 Maven Central] + + %% All stage aggregates everything + wasm-rust-guest.artifact --> all[all
✅ 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. diff --git a/Dockerfile b/Dockerfile index 4ac12d1..56b1731 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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/protoc-gen-go@v1.34 - -# 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 # ============================================================================== @@ -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 diff --git a/README.md b/README.md index 97dc9b5..0a387a7 100644 --- a/README.md +++ b/README.md @@ -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.