├── .cargo
└── config.toml
├── .dockerignore
├── .github
├── actions
│ ├── build
│ │ └── action.yml
│ ├── run-hey-load-test
│ │ └── action.yml
│ ├── setup-env
│ │ └── action.yml
│ └── sign
│ │ └── action.yml
├── dependabot.yml
├── labeler.yml
└── workflows
│ ├── action-build.yml
│ ├── action-check.yml
│ ├── action-test-image.yml
│ ├── action-test-k3s.yml
│ ├── action-test-kind.yml
│ ├── action-test-smoke.yml
│ ├── benchmarks.yml
│ ├── ci.yml
│ ├── docs.yml
│ ├── labeler.yml
│ ├── release-wasi-demo-app.yml
│ ├── release.yml
│ ├── sbom.yml
│ ├── scorecard-analysis.yml
│ └── sign.yml
├── .gitignore
├── .gitmodules
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── Cross.toml
├── LICENSE
├── MAINTAINERS
├── Makefile
├── NOTICE
├── README.md
├── RELEASE.md
├── SECURITY.md
├── art
└── logo
│ ├── runwasi_icon1.svg
│ ├── runwasi_icon2.svg
│ ├── runwasi_icon3.svg
│ ├── runwasi_icon4.svg
│ ├── runwasi_logo_horizontal.svg
│ └── runwasi_logo_icon.svg
├── benches
└── containerd-shim-benchmarks
│ ├── Cargo.toml
│ ├── README.md
│ └── benches
│ └── wasi-demo-app-benchmarks.rs
├── crates
├── containerd-shim-wamr
│ ├── Cargo.toml
│ └── src
│ │ ├── instance.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ └── tests.rs
├── containerd-shim-wasm-test-modules
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ ├── lib.rs
│ │ └── modules
│ │ ├── component-hello-world.wasm
│ │ ├── custom_entrypoint.wat
│ │ ├── exit_code.wat
│ │ ├── fetch_rs.wasm
│ │ ├── has_default_devices.rs
│ │ ├── hello_wasi_http.wasm
│ │ ├── hello_wasi_http_csharp.wasm
│ │ ├── hello_world.wat
│ │ ├── seccomp.rs
│ │ ├── simple_component.wat
│ │ └── unreachable.wat
├── containerd-shim-wasm
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── containerd
│ │ ├── client.rs
│ │ ├── lease.rs
│ │ └── mod.rs
│ │ ├── lib.rs
│ │ ├── sandbox
│ │ ├── context.rs
│ │ ├── mod.rs
│ │ └── path.rs
│ │ ├── shim
│ │ ├── cli.rs
│ │ ├── mod.rs
│ │ ├── shim.rs
│ │ ├── tests.rs
│ │ └── wasm.rs
│ │ ├── sys
│ │ ├── unix
│ │ │ ├── container
│ │ │ │ ├── container.rs
│ │ │ │ ├── executor.rs
│ │ │ │ ├── instance.rs
│ │ │ │ └── mod.rs
│ │ │ ├── mod.rs
│ │ │ └── pid_fd.rs
│ │ └── windows
│ │ │ ├── container
│ │ │ ├── instance.rs
│ │ │ └── mod.rs
│ │ │ └── mod.rs
│ │ ├── test
│ │ ├── mod.rs
│ │ └── signals.rs
│ │ └── testing.rs
├── containerd-shim-wasmedge
│ ├── Cargo.toml
│ └── src
│ │ ├── instance.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ └── tests.rs
├── containerd-shim-wasmer
│ ├── Cargo.toml
│ └── src
│ │ ├── instance.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ └── tests.rs
├── containerd-shim-wasmtime
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── http_proxy.rs
│ │ ├── instance.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ └── tests.rs
├── containerd-shimkit
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── lib.rs
│ │ ├── sandbox
│ │ ├── async_utils.rs
│ │ ├── cli.rs
│ │ ├── error.rs
│ │ ├── instance.rs
│ │ ├── instance_utils.rs
│ │ ├── mod.rs
│ │ ├── oci.rs
│ │ ├── shim
│ │ │ ├── events.rs
│ │ │ ├── instance_data.rs
│ │ │ ├── local.rs
│ │ │ ├── local
│ │ │ │ └── tests.rs
│ │ │ ├── mod.rs
│ │ │ ├── otel.rs
│ │ │ ├── shim.rs
│ │ │ └── task_state.rs
│ │ └── sync.rs
│ │ ├── sys
│ │ ├── unix
│ │ │ ├── metrics.rs
│ │ │ ├── mod.rs
│ │ │ └── stdio.rs
│ │ └── windows
│ │ │ ├── metrics.rs
│ │ │ ├── mod.rs
│ │ │ └── stdio.rs
│ │ └── vendor
│ │ ├── README.md
│ │ ├── containerd_shim
│ │ ├── logger.rs
│ │ ├── mod.rs
│ │ └── sys
│ │ │ ├── mod.rs
│ │ │ └── windows
│ │ │ ├── mod.rs
│ │ │ └── named_pipe_logger.rs
│ │ └── mod.rs
├── oci-tar-builder
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── bin.rs
│ │ └── lib.rs
├── stress-test
│ ├── Cargo.toml
│ ├── README.md
│ ├── protos
│ │ └── github.com
│ │ │ └── containerd
│ │ │ └── containerd
│ │ │ ├── api
│ │ │ ├── runtime
│ │ │ │ └── task
│ │ │ │ │ ├── v2
│ │ │ │ │ └── shim.proto
│ │ │ │ │ └── v3
│ │ │ │ │ └── shim.proto
│ │ │ ├── services
│ │ │ │ └── ttrpc
│ │ │ │ │ └── events
│ │ │ │ │ └── v1
│ │ │ │ │ └── events.proto
│ │ │ └── types
│ │ │ │ ├── mount.proto
│ │ │ │ └── task
│ │ │ │ └── task.proto
│ │ │ └── protobuf
│ │ │ └── plugin
│ │ │ └── fieldpath.proto
│ └── src
│ │ ├── containerd
│ │ ├── client.rs
│ │ ├── containerd.rs
│ │ ├── mod.rs
│ │ ├── shim.rs
│ │ └── task.rs
│ │ ├── main.rs
│ │ ├── mocks
│ │ ├── containerd.rs
│ │ ├── mod.rs
│ │ ├── shim.rs
│ │ ├── task.rs
│ │ └── task_client.rs
│ │ ├── protos.rs
│ │ ├── traits.rs
│ │ └── utils.rs
└── wasi-demo-app
│ ├── .cargo
│ └── config.toml
│ ├── Cargo.toml
│ ├── README.md
│ ├── build.rs
│ └── src
│ └── main.rs
├── cross
├── Dockerfile.gnu
└── Dockerfile.musl
├── docs
├── .gitignore
├── book.toml
├── mermaid-init.js
├── mermaid.min.js
└── src
│ ├── CONTRIBUTING.md
│ ├── README.md
│ ├── RELEASE.md
│ ├── SUMMARY.md
│ ├── art
│ └── logo
│ │ ├── runwasi_icon1.svg
│ │ └── runwasi_icon3.svg
│ ├── assets
│ ├── benchmark-website.png
│ ├── runwasi-architecture.png
│ ├── wasmtime-shim-jeager.png
│ └── wasmtime-shim-tracing-main.png
│ ├── benchmarks.md
│ ├── developer
│ ├── architecture.md
│ ├── docs.md
│ └── roadmap.md
│ ├── getting-started
│ ├── demos.md
│ ├── installation.md
│ └── quickstart.md
│ ├── oci-decision-flow.md
│ ├── opentelemetry.md
│ ├── resources
│ ├── community.md
│ ├── faq.md
│ └── troubleshooting.md
│ └── windows-getting-started.md
├── rust-toolchain.toml
├── rustfmt.toml
├── scripts
├── benchmark-mem.sh
├── bins.sh
├── cleanup-tests.sh
├── crates.jq
├── extract-changelog.sh
├── parse-hey.py
├── setup-cross.sh
├── setup-jeager-and-otel.sh
├── setup-linux.sh
├── setup-windows.sh
├── test-runner.sh
├── verify-jaeger-traces.sh
└── version.sh
├── test
├── k3s
│ └── bootstrap.sh
└── k8s
│ ├── Dockerfile
│ ├── deploy.oci.yaml
│ └── deploy.yaml
└── typos.toml
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.'cfg(windows)']
2 | rustflags = [
3 | "-Adead_code",
4 | "-Awarnings",
5 | ]
6 |
7 | [target.'cfg(unix)']
8 | runner = 'scripts/test-runner.sh'
9 |
10 | [profile.release]
11 | strip = "symbols"
12 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | target
2 | test/k8s/Dockerfile
3 | test/k8s/_out/*
4 | .github
5 | out
6 | *.md
7 | LICENSE
8 | .gitignore
9 | .dockerignore
10 | docs
11 | bin/
12 | Dockerfile
13 | docker-bake.hcl
14 | release/
15 |
--------------------------------------------------------------------------------
/.github/actions/build/action.yml:
--------------------------------------------------------------------------------
1 | name: Build artifacts and pull images
2 | description: 'Build artifacts and pull images'
3 |
4 | runs:
5 | using: composite
6 | steps:
7 | - name: Build and pull
8 | shell: bash
9 | run: |
10 | set -euxo pipefail
11 | make OPT_PROFILE=release build install pull
--------------------------------------------------------------------------------
/.github/actions/run-hey-load-test/action.yml:
--------------------------------------------------------------------------------
1 | name: "Run hey load test"
2 | description: "Composite action to run hey load test"
3 |
4 | inputs:
5 | github-token:
6 | description: "GitHub token"
7 | required: true
8 |
9 | runs:
10 | using: "composite"
11 | steps:
12 | - name: Install hey
13 | shell: bash
14 | run: |
15 | curl -LO https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64
16 | chmod +x hey_linux_amd64
17 | sudo mv hey_linux_amd64 /usr/local/bin/hey
18 | hey --help
19 | - name: Run hey benchmark
20 | shell: bash
21 | run: |
22 | hey -n 100000 -c 50 http://127.0.0.1:8080 > raw-output.txt
23 | python3 ./scripts/parse-hey.py raw-output.txt
24 | cat latency_results.json
25 | cat throughput_results.json
26 | - name: Report Throughput results
27 | uses: benchmark-action/github-action-benchmark@v1.20.4
28 | with:
29 | name: "HTTP Throughput"
30 | tool: "customBiggerIsBetter"
31 | output-file-path: throughput_results.json
32 | github-token: ${{ inputs.github-token }}
33 | auto-push: ${{ github.event_name == 'schedule' }}
34 | summary-always: true
35 | alert-threshold: "130%"
36 | fail-on-alert: ${{ github.event_name == 'schedule' }}
37 | - name: Report Latency results
38 | if : ${{ github.event_name == 'schedule' }}
39 | uses: benchmark-action/github-action-benchmark@v1.20.4
40 | with:
41 | name: "HTTP Latency"
42 | tool: "customSmallerIsBetter"
43 | output-file-path: latency_results.json
44 | github-token: ${{ inputs.github-token }}
45 | auto-push: true
46 | summary-always: true
47 | alert-threshold: "130%"
48 | fail-on-alert: true
49 |
50 | # If the event is not a schedule, we'd run into a problem of the failed `git fetch` command,
51 | # which attempts to update the local `gh-pages` branch with changes from the remote repository
52 | # but failed because the update is non-fast-forward. It failed because the previous step
53 | # `benchmark-action/github-action-benchmark` has already committed the changes to the local, so
54 | # it creates a conflict with the remote repository.
55 | #
56 | # The workaround is to use the `external-data-json-path` option to tell the action to not
57 | # attempt to update the local `gh-pages` branch.
58 | - name: Report Latency results
59 | if : ${{ github.event_name != 'schedule' }}
60 | uses: benchmark-action/github-action-benchmark@v1.20.4
61 | with:
62 | name: "HTTP Latency"
63 | tool: "customSmallerIsBetter"
64 | output-file-path: latency_results.json
65 | github-token: ${{ inputs.github-token }}
66 | summary-always: true
67 | alert-threshold: "130%"
68 | fail-on-alert: false
69 | external-data-json-path: ./cache/latency_results.json
--------------------------------------------------------------------------------
/.github/actions/setup-env/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup Environment
2 | description: 'Common environment setup'
3 |
4 | runs:
5 | using: composite
6 | steps:
7 | - name: Setup OS dependencies
8 | shell: bash
9 | run: ./scripts/setup-$(echo "$RUNNER_OS" | tr '[:upper:]' '[:lower:]').sh
--------------------------------------------------------------------------------
/.github/actions/sign/action.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json
2 |
3 | name: Signing
4 | description: 'Signs binaries using cosign'
5 |
6 | inputs:
7 | runtime:
8 | required: true
9 |
10 | runs:
11 | using: "composite"
12 | steps:
13 | - name: Setup cosign for signing
14 | uses: sigstore/cosign-installer@v3.7.0
15 | with:
16 | cosign-release: 'v2.2.2'
17 | - name: Sign the binaries
18 | shell: bash
19 | run: |
20 | # Check if there's any files to archive as tar fails otherwise
21 | if stat dist/bin/* >/dev/null 2>&1; then
22 | echo "::notice::Signing the binary"
23 | cosign sign-blob --yes \
24 | --output-signature containerd-shim-${{ inputs.runtime }}-v1.sig \
25 | --output-certificate containerd-shim-${{ inputs.runtime }}-v1.pem \
26 | --bundle containerd-shim-${{ inputs.runtime }}-v1.bundle \
27 | dist/bin/containerd-shim-${{ inputs.runtime }}-v1
28 |
29 | # Copy the certs to the dist/bin folder
30 | cp *.sig dist/bin/
31 | cp *.pem dist/bin/
32 | else
33 | echo "::warning::No files to sign"
34 | fi
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | - package-ecosystem: cargo
8 | directory: "/"
9 | schedule:
10 | interval: "weekly"
11 | groups:
12 | wasmtime-deps:
13 | patterns:
14 | - "wasmtime"
15 | - "wasmtime-wasi"
16 | - "wasi-common"
17 | patch:
18 | update-types:
19 | - "patch"
--------------------------------------------------------------------------------
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | # Crate labels
2 | C-containerd-shim-wasm:
3 | - changed-files:
4 | - any-glob-to-any-file: crates/containerd-shim-wasm/**/*
5 |
6 | C-wamr:
7 | - changed-files:
8 | - any-glob-to-any-file: crates/containerd-shim-wamr/**/*
9 |
10 | C-wasmedge:
11 | - changed-files:
12 | - any-glob-to-any-file: crates/containerd-shim-wasmedge/**/*
13 |
14 | C-wasmer:
15 | - changed-files:
16 | - any-glob-to-any-file: crates/containerd-shim-wasmer/**/*
17 |
18 | C-wasmtime:
19 | - changed-files:
20 | - any-glob-to-any-file: crates/containerd-shim-wasmtime/**/*
21 |
22 | C-common:
23 | - changed-files:
24 | - any-glob-to-any-file:
25 | - crates/containerd-shim-wasm-test-modules/**/*
26 | - crates/oci-tar-builder/**/*
27 | - crates/wasi-demo-app/**/*
28 | - crates/stress-test/**/*
29 |
30 | # Change type labels
31 | T-CI:
32 | - changed-files:
33 | - any-glob-to-any-file: [".github/**", "scripts/**", "cross/**", "Makefile"]
34 |
35 | T-docs:
36 | - changed-files:
37 | - any-glob-to-any-file: ["docs/**", "*.md", "CONTRIBUTING.md", "RELEASE.md"]
38 |
39 | T-tests:
40 | - changed-files:
41 | - any-glob-to-any-file: ["test/**"]
42 |
43 | T-benchmarks:
44 | - changed-files:
45 | - any-glob-to-any-file: ["benches/**"]
--------------------------------------------------------------------------------
/.github/workflows/action-check.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json
2 |
3 | name: Run lint
4 |
5 | on:
6 | workflow_call:
7 | inputs:
8 | os:
9 | required: true
10 | type: string
11 | runtime:
12 | required: true
13 | type: string
14 |
15 | jobs:
16 | check:
17 | name: lint on ${{ inputs.os }}
18 | runs-on: ${{ inputs.os }}
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: actions-rust-lang/setup-rust-toolchain@v1
22 | with:
23 | components: rustfmt, clippy
24 | rustflags: '' #Disable. By default this action sets environment variable is set to -D warnings. We manage this in the Makefile
25 | - uses: ./.github/actions/setup-env
26 | - run:
27 | # needed to run rustfmt in nightly toolchain
28 | rustup toolchain install nightly --component rustfmt
29 | - name: Run checks
30 | run: make check-${{ inputs.runtime }}
31 |
--------------------------------------------------------------------------------
/.github/workflows/action-test-image.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json
2 |
3 | name: Run end to end tests on kind
4 |
5 | on:
6 | workflow_call:
7 | inputs:
8 | image:
9 | type: string
10 | default: img
11 |
12 | jobs:
13 | test-image:
14 | name: build test ${{ inputs.image }}
15 | runs-on: "ubuntu-latest"
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: build
19 | run: make dist/${{ inputs.image }}.tar
20 | - name: Upload artifacts
21 | uses: actions/upload-artifact@master
22 | with:
23 | name: test-${{ inputs.image }}
24 | path: dist/${{ inputs.image }}.tar
25 |
--------------------------------------------------------------------------------
/.github/workflows/action-test-k3s.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json
2 |
3 | name: Run end to end tests on k3s
4 |
5 | on:
6 | workflow_call:
7 | inputs:
8 | os:
9 | required: true
10 | type: string
11 | runtime:
12 | required: true
13 | type: string
14 |
15 | jobs:
16 | e2e-k3s:
17 | name: e2e k3s test on ${{ inputs.os }}
18 | runs-on: ${{ inputs.os }}
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: ./.github/actions/setup-env
22 | - name: Download artifacts
23 | uses: actions/download-artifact@master
24 | with:
25 | name: containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl
26 | path: dist
27 | - name: Unpack artifats
28 | shell: bash
29 | run: |
30 | mkdir -p dist/bin
31 | tar -xzf dist/containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl.tar.gz -C dist/bin
32 | - name: Download test image
33 | uses: actions/download-artifact@master
34 | with:
35 | name: test-img
36 | path: dist
37 | - name: run
38 | timeout-minutes: 10
39 | run: make test/k3s-${{ inputs.runtime }}
40 | # only runs when the previous step fails
41 | - name: inspect failed pods
42 | if: failure()
43 | run: |
44 | sudo bin/k3s kubectl get pods --all-namespaces
45 | sudo bin/k3s kubectl describe pods --all-namespaces
46 | - name: tar logs
47 | if: failure()
48 | run: |
49 | sudo journalctl -u k3s-runwasi > k3s.log
50 | sudo tar czf k3s-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz -C . k3s.log -C /var/log/pods . -C /var/lib/rancher/k3s/agent/containerd/ containerd.log
51 | sudo chown ${USER}:${USER} k3s-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz
52 | - name: upload logs
53 | if: failure()
54 | uses: actions/upload-artifact@master
55 | with:
56 | name: k3s-logs-${{ inputs.runtime }}-${{ inputs.os }}
57 | path: k3s-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz
58 | - name: cleanup
59 | if: always()
60 | run: make test/k3s/clean
61 |
--------------------------------------------------------------------------------
/.github/workflows/action-test-kind.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json
2 |
3 | name: Run end to end tests on kind
4 |
5 | on:
6 | workflow_call:
7 | inputs:
8 | os:
9 | required: true
10 | type: string
11 | runtime:
12 | required: true
13 | type: string
14 | image:
15 | type: string
16 | default: img
17 | test-command:
18 | type: string
19 | required: true
20 |
21 | jobs:
22 | e2e-kind:
23 | name: e2e kind test on ${{ inputs.os }} with ${{ inputs.image }}
24 | runs-on: ${{ inputs.os }}
25 | steps:
26 | - uses: actions/checkout@v4
27 | - uses: ./.github/actions/setup-env
28 | - name: Download artifacts
29 | uses: actions/download-artifact@master
30 | with:
31 | name: containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl
32 | path: dist
33 | - name: Unpack artifats
34 | shell: bash
35 | run: |
36 | mkdir -p dist/bin
37 | tar -xzf dist/containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl.tar.gz -C dist/bin
38 | - name: Download test image
39 | uses: actions/download-artifact@master
40 | with:
41 | name: test-${{ inputs.image }}
42 | path: dist
43 | - name: run
44 | timeout-minutes: 10
45 | run: ${{ inputs.test-command }}
46 | # only runs when the previous step fails
47 | - name: inspect failed pods
48 | if: failure()
49 | run: |
50 | kubectl get pods --all-namespaces
51 | kubectl describe pods --all-namespaces
52 | - name: tar logs
53 | if: failure()
54 | run: |
55 | bin/kind export logs ./kind-logs --name containerd-wasm
56 | tar czf kind-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz -C ./kind-logs .
57 | - name: upload logs
58 | if: failure()
59 | uses: actions/upload-artifact@master
60 | with:
61 | name: kind-logs-${{ inputs.runtime }}-${{ inputs.os }}
62 | path: kind-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz
63 | - name: cleanup
64 | if: always()
65 | run: make test/k8s/clean
66 |
--------------------------------------------------------------------------------
/.github/workflows/action-test-smoke.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json
2 |
3 | name: Run smoke tests
4 |
5 | on:
6 | workflow_call:
7 | inputs:
8 | os:
9 | required: true
10 | type: string
11 | runtime:
12 | required: true
13 | type: string
14 |
15 | jobs:
16 | smoke-test:
17 | name: smoke test on ${{ inputs.os }}
18 | runs-on: ${{ inputs.os }}
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: ./.github/actions/setup-env
22 | - name: Download artifacts
23 | uses: actions/download-artifact@master
24 | with:
25 | name: containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl
26 | path: dist
27 | - name: Unpack artifats
28 | shell: bash
29 | run: |
30 | mkdir -p dist/bin
31 | tar -xzf dist/containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl.tar.gz -C dist/bin
32 | - name: Download test image
33 | uses: actions/download-artifact@master
34 | with:
35 | name: test-img
36 | path: dist
37 | - name: Enable OTLP
38 | run: |
39 | sudo ./scripts/setup-jeager-and-otel.sh
40 | - name: Run wasi-demo-app using ctr
41 | timeout-minutes: 5
42 | run: |
43 | ls -alh dist
44 | ls -alh dist/bin
45 | sudo cp -f dist/bin/* /usr/local/bin
46 | make pull-app
47 | sudo ctr run --rm --runtime=io.containerd.${{ inputs.runtime }}.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm /wasi-demo-app.wasm echo 'hello'
48 | - name: Verify Jaeger traces
49 | run: |
50 | sleep 5
51 | sudo ./scripts/verify-jaeger-traces.sh
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Deploy mdbook documentation
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 | workflow_dispatch:
9 |
10 | jobs:
11 | changes:
12 | runs-on: ubuntu-22.04
13 | timeout-minutes: 10
14 | outputs:
15 | dirs: ${{ steps.filter.outputs.changes }}
16 | steps:
17 | - uses: actions/checkout@v4
18 | - uses: dorny/paths-filter@v3
19 | id: filter
20 | with:
21 | filters: |
22 | docs: docs/**
23 | deploy:
24 | needs: [changes]
25 | if: ${{ !contains(needs.changes.outputs.dirs, '[]') }}
26 | runs-on: ubuntu-22.04
27 | timeout-minutes: 10
28 | concurrency:
29 | group: ${{ github.workflow }}-${{ github.ref }}
30 | steps:
31 | - uses: actions/checkout@v4
32 | - uses: actions-rust-lang/setup-rust-toolchain@v1
33 | - name: install mermaid preprocessor
34 | run: cargo install mdbook mdbook-mermaid
35 | - name: Build mdbook
36 | working-directory: ./docs
37 | run: mdbook build
38 | - name: Deploy
39 | uses: peaceiris/actions-gh-pages@v4
40 | if: ${{ github.ref == 'refs/heads/main' }}
41 | with:
42 | github_token: ${{ secrets.GITHUB_TOKEN }}
43 | publish_dir: ./docs/book
44 | keep_files: true
45 |
46 | post_deploy_link_check:
47 | name: Verify links after deployment
48 | needs: [deploy]
49 | if: ${{ github.ref == 'refs/heads/main' }}
50 | permissions:
51 | contents: read
52 | issues: write
53 | timeout-minutes: 15
54 | runs-on: ubuntu-latest
55 | steps:
56 | - name: Wait for GitHub Pages to update
57 | run: sleep 120
58 | - name: Check all links on runwasi.dev
59 | uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
60 | with:
61 | fail: true # Fail CI if broken links found on the live site
62 | format: markdown
63 | jobSummary: true
64 |
65 | - name: Setup GitHub CLI
66 | if: ${{ failure() }}
67 | uses: cli/setup-gh@v1
68 |
69 | - name: Create or update issue on link check failure
70 | if: ${{ failure() }}
71 | shell: bash
72 | env:
73 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
74 | run: |
75 | TODAY=$(date +"%Y-%m-%d")
76 | EXISTING_ISSUE=$(gh issue list --repo ${{ github.repository }} --label broken-links --state open --json number --jq ".[0].number")
77 | ISSUE_BODY="# Broken Links Report ($TODAY)
78 |
79 | The post-deployment link check has detected broken links on the runwasi.dev website.
80 | Please see the [link checker workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
81 |
82 | > This issue was automatically generated from the link checker workflow."
83 |
84 | if [ -n "$EXISTING_ISSUE" ]; then
85 | gh issue comment $EXISTING_ISSUE --repo ${{ github.repository }} --body "New broken links were detected in the workflow run on $TODAY. Please check the latest workflow run for details: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
86 | else
87 | gh issue create --repo ${{ github.repository }} --title "Broken links detected on runwasi.dev" --body "$ISSUE_BODY" --label documentation --label broken-links
88 | fi
89 |
--------------------------------------------------------------------------------
/.github/workflows/labeler.yml:
--------------------------------------------------------------------------------
1 | name: PR Labeler
2 | on:
3 | # Runs workflow when activity on a PR in the workflow's repository occurs.
4 | pull_request_target:
5 |
6 | permissions:
7 | contents: read
8 | pull-requests: write
9 |
10 | jobs:
11 | label:
12 | runs-on: ubuntu-latest
13 | timeout-minutes: 5
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/labeler@v5
17 | with:
18 | repo-token: ${{ secrets.GITHUB_TOKEN }}
19 | sync-labels: true
--------------------------------------------------------------------------------
/.github/workflows/sbom.yml:
--------------------------------------------------------------------------------
1 | name: Generate SBOMs
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | image-name:
7 | type: string
8 | required: true
9 | image-digest:
10 | type: string
11 | required: true
12 |
13 | jobs:
14 | sbom:
15 | name: Generate SBOM, sign and attach them to OCI image
16 | permissions:
17 | packages: write
18 | id-token: write
19 |
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Sanitize image name
23 | run: |
24 | image="${{ inputs.image-name }}"
25 | image="${image//_/-}"
26 | echo "image=$image" >> $GITHUB_ENV
27 |
28 | - name: Install cosign
29 | uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
30 |
31 | - name: Install syft
32 | uses: anchore/sbom-action/download-syft@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0
33 |
34 | - name: Login to GitHub Container Registry
35 | uses: docker/login-action@v3
36 | with:
37 | registry: ghcr.io
38 | username: ${{ github.repository }}
39 | password: ${{ secrets.GITHUB_TOKEN }}
40 |
41 | - name: Find repository name
42 | shell: bash
43 | run: |
44 | set -e
45 | IMG_REPOSITORY_NAME=$( echo ${{ github.repository }} | awk '{print tolower($0)}' )
46 | echo IMG_REPOSITORY_NAME=${IMG_REPOSITORY_NAME} >> $GITHUB_ENV
47 |
48 | - name: Create SBOM file
49 | shell: bash
50 | run: |
51 | SYFT=$(which syft)
52 | sudo $SYFT \
53 | -o spdx-json \
54 | --file $image-sbom.spdx \
55 | ghcr.io/${{ env.IMG_REPOSITORY_NAME }}/$image@${{ inputs.image-digest }}
56 |
57 | - name: Sign SBOM file
58 | run: |
59 | cosign sign-blob --yes \
60 | --output-certificate $image-sbom.spdx.cert \
61 | --output-signature $image-sbom.spdx.sig \
62 | $image-sbom.spdx
63 |
64 | - name: Attach SBOM to container image
65 | shell: bash
66 | run: |
67 | cosign attach \
68 | sbom --sbom $image-sbom.spdx \
69 | ghcr.io/${{ env.IMG_REPOSITORY_NAME }}/$image@${{ inputs.image-digest }}
70 |
71 | - name: Sign SBOM file pushed to OCI registry
72 | shell: bash
73 | run: |
74 | set -e
75 | SBOM_TAG="$(echo ${{ inputs.image-digest }} | sed -e 's/:/-/g').sbom"
76 | cosign sign --yes \
77 | ghcr.io/${{ env.IMG_REPOSITORY_NAME }}/$image:${SBOM_TAG}
78 |
79 | - name: Upload SBOMs as artifacts
80 | uses: actions/upload-artifact@v4
81 | with:
82 | name: sbom
83 | path: $image-sbom-*
--------------------------------------------------------------------------------
/.github/workflows/scorecard-analysis.yml:
--------------------------------------------------------------------------------
1 | name: Scorecard analysis workflow
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '30 1 * * 6' # 1:30 AM UTC on Saturday
6 | pull_request:
7 | paths:
8 | - ".github/workflows/scorecard-analysis.yml"
9 |
10 | permissions: read-all
11 |
12 | jobs:
13 | analysis:
14 | name: Scorecard analysis
15 | runs-on: ubuntu-latest
16 | permissions:
17 | # Needed for Code scanning upload
18 | security-events: write
19 | # Needed for GitHub OIDC token if publish_results is true
20 | id-token: write
21 |
22 | steps:
23 | - name: "Checkout code"
24 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25 | with:
26 | persist-credentials: false
27 |
28 | - name: "Run analysis"
29 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
30 | with:
31 | results_file: results.sarif
32 | results_format: sarif
33 | # Publish the results for public repositories to enable scorecard badges. For more details, see
34 | # https://github.com/ossf/scorecard-action#publishing-results.
35 | # For now, we don't want to publish the results
36 | publish_results: false
37 |
38 | - name: "Upload artifact"
39 | uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
40 | with:
41 | name: SARIF file
42 | path: results.sarif
43 | retention-days: 5
44 |
45 | # Upload the results to GitHub's code scanning dashboard
46 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard
47 | - name: "Upload to code-scanning"
48 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
49 | with:
50 | sarif_file: results.sarif
--------------------------------------------------------------------------------
/.github/workflows/sign.yml:
--------------------------------------------------------------------------------
1 | name: Sign image
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | image-name:
7 | type: string
8 | required: true
9 | image-digest:
10 | type: string
11 | required: true
12 |
13 | jobs:
14 | sign:
15 | name: Sign image
16 | permissions:
17 | packages: write
18 | id-token: write
19 |
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Sanitize image name
23 | run: |
24 | image="${{ inputs.image-name }}"
25 | image="${image//_/-}"
26 | echo "image=$image" >> $GITHUB_ENV
27 |
28 | - name: Install cosign
29 | uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
30 |
31 | - name: Login to GitHub Container Registry
32 | uses: docker/login-action@v3
33 | with:
34 | registry: ghcr.io
35 | username: ${{ github.repository }}
36 | password: ${{ secrets.GITHUB_TOKEN }}
37 |
38 | - name: Sign container image
39 | run: |
40 | IMG_REPOSITORY_NAME=$( echo ${{ github.repository }} | awk '{print tolower($0)}' )
41 | cosign sign --yes \
42 | ghcr.io/${IMG_REPOSITORY_NAME}/$image@${{ inputs.image-digest }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /dist/
3 | /test/out/img.tar
4 | /test/k8s/_out
5 | **/target/
6 | /release/
7 | /.vscode/
8 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/.gitmodules
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "crates/containerd-shim-wasm",
4 | "crates/containerd-shim-wasm-test-modules",
5 | "crates/wasi-demo-app",
6 | "crates/oci-tar-builder",
7 | "crates/containerd-shim-wasmedge",
8 | "crates/containerd-shim-wasmtime",
9 | "crates/containerd-shim-wasmer",
10 | "crates/containerd-shim-wamr",
11 | "crates/containerd-shimkit",
12 | "crates/stress-test",
13 | "benches/containerd-shim-benchmarks",
14 | ]
15 | resolver = "2"
16 |
17 | [workspace.package]
18 | edition = "2024"
19 | version = "0.4.0"
20 | license = "Apache-2.0"
21 | readme = "README.md"
22 | repository = "https://github.com/containerd/runwasi"
23 | homepage = "https://github.com/containerd/runwasi"
24 |
25 | [workspace.dependencies]
26 | anyhow = "1.0"
27 | chrono = { version = "0.4", default-features = false, features = ["clock"] }
28 | containerd-shim = "0.8"
29 | containerd-shimkit = { path = "crates/containerd-shimkit", version = "0.1.1" }
30 | containerd-shim-wasm = { path = "crates/containerd-shim-wasm", version = "1.0.0" }
31 | containerd-shim-wasm-test-modules = { path = "crates/containerd-shim-wasm-test-modules", version = "0.4.0"}
32 | oci-tar-builder = { path = "crates/oci-tar-builder", version = "0.4.0" }
33 | env_logger = "0.11"
34 | libc = "0.2.172"
35 | libcontainer = { version = "0.5", default-features = false }
36 | log = "0.4"
37 | nix = "0.29"
38 | oci-spec = { version = "0.7.1", features = ["runtime"] }
39 | protobuf = "=3.2"
40 | serde = "1.0"
41 | serde_json = "1.0"
42 | sha256 = "1.6.0"
43 | tar = "0.4"
44 | tempfile = "3.19"
45 | thiserror = "2.0"
46 | wat = "1.228"
47 | windows-sys = "0.59"
48 | serial_test = "3"
49 | tracing = "0.1"
50 | hyper = "1.6.0"
51 | tokio = { version = "1.45.1", default-features = false }
52 | tokio-util = { version = "0.7", default-features = false }
53 | cfg-if = "1.0"
54 |
55 | # wasmtime
56 | wasmtime = { version = "27.0.0", features = ["async"] }
57 | wasmtime-wasi = { version = "27.0.0" }
58 | wasmtime-wasi-http = { version = "27.0.0" }
59 |
60 | [profile.release]
61 | panic = "abort"
62 |
--------------------------------------------------------------------------------
/Cross.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | default-target = "x86_64-unknown-linux-musl"
3 |
4 | [target.aarch64-unknown-linux-musl]
5 | dockerfile = "cross/Dockerfile.musl"
6 |
7 | [target.x86_64-unknown-linux-musl]
8 | dockerfile = "cross/Dockerfile.musl"
9 |
10 | [target.aarch64-unknown-linux-gnu]
11 | dockerfile = "cross/Dockerfile.gnu"
12 |
13 | [target.x86_64-unknown-linux-gnu]
14 | dockerfile = "cross/Dockerfile.gnu"
15 |
--------------------------------------------------------------------------------
/MAINTAINERS:
--------------------------------------------------------------------------------
1 | # runwasi maintainers
2 | #
3 | # As a containerd sub-project, containerd maintainers are also included from https://github.com/containerd/project/blob/main/MAINTAINERS.
4 | # See https://github.com/containerd/project/blob/main/GOVERNANCE.md for description of maintainer role
5 | #
6 | # COMMITTERS
7 | # GitHub ID, Name, Email address
8 | "cpuguy","Brian Goff","cpuguy83@gmail.com"
9 | "mossaka","Jiaxiao Zhou","jiazho@microsoft.com"
10 | "danbugs","Dan Chiarlone","danilochiarlone@gmail.com"
11 | "devigned","David Justice","david@justice.dev"
12 | "jprendes","Jorge Prendes","jorge.prendes@gmail.com"
13 | "jsturtevant","James Sturtevant","jstur@microsoft.com"
14 |
15 | # REVIEWERS
16 | # GitHub ID, Name, Email address
17 | "ipuustin","Ismo Puustinen","ismo.puustinen@intel.com"
18 | "rumpl","Djordje Lukic","djordje.lukic@docker.com"
19 | "utam0k","Toru Komatsu","k0ma@utam0k.jp"
20 | "andreiltd","Tomasz Andrzejak","andreiltd@gmail.com"
21 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Docker | Containerd
2 | Copyright 2022 Docker, Inc.
3 |
4 | This product includes software developed at Deis Labs (https://github.com/deislabs)
5 | but ownership transitioned from deislabs to the containerd org since 2022.
6 |
7 | This product includes software developed at
8 | The Containerd (https://github.com/containerd/runwasi).
9 |
10 | The Initial Developer of some parts of the file
11 | crates/containerd-shim-wasm/src/sandbox/shim.rs, which are copied from,
12 | derived from, or inspired by Youki (https://github.com/containers/youki)
13 | Copyright 2021 - 2022 Youki. All Rights Reserved.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
runwasi
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | This is a project to facilitate running wasm workloads managed by containerd either directly (ie. through ctr) or as directed by Kubelet via the CRI plugin.
18 | It is intended to be a (rust) library that you can take and integrate with your wasm host.
19 | Included in the repository is a PoC for running a plain wasi host (ie. no extra host functions except to support wasi system calls).
20 |
21 | ## Community
22 |
23 | - If you haven't joined the CNCF slack yet, you can do so [here](https://slack.cncf.io/).
24 | - Come join us on our [slack channel #runwasi](https://cloud-native.slack.com/archives/C04LTPB6Z0V) on the CNCF slack.
25 | - Public Community Call on Tuesdays every other week at 9:00 AM PT: [Zoom](https://zoom.us/my/containerd?pwd=bENmREpnSGRNRXdBZWV5UG8wbU1oUT09), [Meeting Notes](https://docs.google.com/document/d/1aOJ-O7fgMyRowHD0kOoA2Z_4d19NyAvvdqOkZO3Su_M/edit?usp=sharing)
26 |
27 | See our [Community Page](https://runwasi.dev/resources/community.html) for more ways to get involved.
28 |
29 | ## Documentation
30 |
31 | For comprehensive documentation, visit our [Documentation Site](https://runwasi.dev/).
32 |
33 | For `containerd-shim-wasm` crate documentation, visit [containerd-shim-wasm](https://docs.rs/containerd-shim-wasm).
34 |
35 | ## Quick Start
36 |
37 | ### Installation
38 |
39 | ```terminal
40 | make build
41 | sudo make install
42 | ```
43 |
44 | For detailed installation instructions, see the [Installation Guide](https://runwasi.dev/getting-started/installation.html).
45 |
46 | ### Running an Example
47 |
48 | ```terminal
49 | # Pull the image
50 | sudo ctr images pull ghcr.io/containerd/runwasi/wasi-demo-app:latest
51 |
52 | # Run the example
53 | sudo ctr run --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm
54 | ```
55 |
56 | For more examples and detailed usage, see the [Demos](https://runwasi.dev/getting-started/demos.html).
57 |
58 | ## Projects Using Runwasi
59 |
60 | Check out these projects that build on top of runwasi:
61 | - [spinkube/containerd-shim-spin](https://github.com/spinkube/containerd-shim-spin)
62 | - [deislabs/containerd-wasm-shims](https://github.com/deislabs/containerd-wasm-shims)
63 |
64 | ## Contributing
65 |
66 | To begin contributing, please read our [Contributing Guide](https://runwasi.dev/CONTRIBUTING.html).
67 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | See https://github.com/containerd/project/blob/main/SECURITY.md for reporting a vulnerability.
2 |
--------------------------------------------------------------------------------
/benches/containerd-shim-benchmarks/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "containerd-shim-benchmarks"
3 | version.workspace = true
4 | edition.workspace = true
5 |
6 | [dev-dependencies]
7 | criterion = { version = "0.5", features = ["html_reports"] }
8 |
9 | [[bench]]
10 | name = "wasi-demo-app-benchmarks"
11 | harness = false
12 |
--------------------------------------------------------------------------------
/benches/containerd-shim-benchmarks/README.md:
--------------------------------------------------------------------------------
1 | # Containerd Shim Benchmarks
2 |
3 | This directory contains benchmarks for testing various aspects of the containerd shims:
4 |
5 | ## Running the Benchmarks
6 |
7 | First, build and install the shims:
8 | ```bash
9 | make build
10 | sudo make install
11 | ```
12 |
13 | Then, pull the wasi-demo-app:
14 | ```bash
15 | make pull
16 | ```
17 |
18 | To run all benchmarks:
19 | ```bash
20 | cargo bench -p containerd-shim-benchmarks
21 | ```
22 |
23 | Note: The benchmarks require sudo access to run containerd commands. Make sure you have the necessary permissions configured.
24 |
--------------------------------------------------------------------------------
/benches/containerd-shim-benchmarks/benches/wasi-demo-app-benchmarks.rs:
--------------------------------------------------------------------------------
1 | use std::process::Command;
2 | use std::time::{Duration, Instant};
3 |
4 | use criterion::{Criterion, criterion_group, criterion_main};
5 |
6 | struct TestCase<'a> {
7 | image: &'a str,
8 | entrypoint: &'a str,
9 | args: &'a [&'a str],
10 | expected: &'a str,
11 | }
12 |
13 | static RUNTIMES: &[&str] = &["wasmtime", "wasmedge", "wasmer", "wamr"];
14 | static TEST_CASES: &[TestCase] = &[
15 | TestCase {
16 | image: "ghcr.io/containerd/runwasi/wasi-demo-app:latest",
17 | entrypoint: "wasi-demo-app.wasm",
18 | args: &["echo", "hello"],
19 | expected: "hello",
20 | },
21 | TestCase {
22 | image: "ghcr.io/containerd/runwasi/wasi-demo-oci:latest",
23 | entrypoint: "wasi-demo-oci.wasm",
24 | args: &["echo", "hello"],
25 | expected: "hello",
26 | },
27 | ];
28 |
29 | fn run_container(runtime: &str, test_case: &TestCase, verify_output: F) -> Duration
30 | where
31 | F: Fn(&str),
32 | {
33 | let start = Instant::now();
34 | let mut cmd = Command::new("sudo");
35 | cmd.args([
36 | "ctr",
37 | "run",
38 | "--rm",
39 | &format!("--runtime=io.containerd.{}.v1", runtime),
40 | test_case.image,
41 | "testwasm",
42 | test_case.entrypoint,
43 | ]);
44 | cmd.args(test_case.args);
45 |
46 | let output = cmd.output().expect("Failed to execute command");
47 | if !output.status.success() {
48 | panic!(
49 | "Container failed to run: {}",
50 | String::from_utf8_lossy(&output.stderr)
51 | );
52 | }
53 |
54 | let stdout = String::from_utf8_lossy(&output.stdout);
55 | verify_output(&stdout);
56 |
57 | start.elapsed()
58 | }
59 |
60 | fn benchmark_image(c: &mut Criterion) {
61 | let mut group = c.benchmark_group("end-to-end");
62 | for runtime in RUNTIMES {
63 | for test_case in TEST_CASES {
64 | let image_base_name = test_case
65 | .image
66 | .rsplit('/')
67 | .next()
68 | .unwrap_or(test_case.image);
69 | let bench_name = format!("{}/{}", runtime, image_base_name);
70 | group.bench_function(&bench_name, |b| {
71 | b.iter(|| {
72 | run_container(runtime, test_case, |stdout| {
73 | assert!(stdout.contains(test_case.expected));
74 | })
75 | });
76 | });
77 | }
78 | }
79 | group.finish();
80 | }
81 |
82 | criterion_group! {
83 | name = benches;
84 | config = Criterion::default()
85 | .sample_size(10)
86 | .warm_up_time(Duration::from_secs(3));
87 | targets = benchmark_image
88 | }
89 |
90 | criterion_main!(benches);
91 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wamr/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "containerd-shim-wamr"
3 | version = "0.2.0"
4 | edition.workspace = true
5 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
6 |
7 | [dependencies]
8 | anyhow = { workspace = true }
9 | containerd-shim-wasm = { workspace = true, features = ["opentelemetry"] }
10 | log = { workspace = true }
11 |
12 | [target.'cfg(unix)'.dependencies]
13 | wamr-rust-sdk = { git = "https://github.com/bytecodealliance/wamr-rust-sdk", tag = "v1.1.0" }
14 |
15 | [dev-dependencies]
16 | containerd-shim-wasm = { workspace = true, features = ["testing"] }
17 | serial_test = { workspace = true }
18 |
19 | [[bin]]
20 | name = "containerd-shim-wamr-v1"
21 | path = "src/main.rs"
22 |
23 | [package.metadata.cargo-machete]
24 | ignored = ["log"]
25 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wamr/src/instance.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Context, Result};
2 | use containerd_shim_wasm::sandbox::Sandbox;
3 | use containerd_shim_wasm::sandbox::context::{Entrypoint, RuntimeContext};
4 | use containerd_shim_wasm::shim::{Shim, Version, version};
5 | use wamr_rust_sdk::function::Function;
6 | use wamr_rust_sdk::instance::Instance as WamrInst;
7 | use wamr_rust_sdk::module::Module;
8 | use wamr_rust_sdk::runtime::Runtime;
9 | use wamr_rust_sdk::wasi_context::WasiCtxBuilder;
10 |
11 | pub struct WamrShim;
12 |
13 | pub struct WamrSandbox {
14 | runtime: Runtime,
15 | }
16 |
17 | unsafe impl Send for WamrSandbox {}
18 | unsafe impl Sync for WamrSandbox {}
19 |
20 | impl Default for WamrSandbox {
21 | fn default() -> Self {
22 | let runtime = Runtime::new().unwrap();
23 | Self { runtime }
24 | }
25 | }
26 |
27 | impl Shim for WamrShim {
28 | type Sandbox = WamrSandbox;
29 |
30 | fn name() -> &'static str {
31 | "wamr"
32 | }
33 |
34 | fn version() -> Version {
35 | version!()
36 | }
37 | }
38 |
39 | impl Sandbox for WamrSandbox {
40 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result {
41 | let args = ctx.args();
42 | let envs = ctx.envs();
43 | let Entrypoint {
44 | source, func, name, ..
45 | } = ctx.entrypoint();
46 |
47 | let wasm_bytes = source
48 | .as_bytes()
49 | .context("Failed to get bytes from source")?;
50 |
51 | log::info!("Create a WAMR module");
52 |
53 | // TODO: error handling isn't ideal
54 |
55 | let mod_name = name.unwrap_or_else(|| "main".to_string());
56 |
57 | let mut module = Module::from_buf(&self.runtime, &wasm_bytes, &mod_name)
58 | .context("Failed to create module from bytes")?;
59 |
60 | log::info!("Create a WASI context");
61 |
62 | let wasi_ctx = WasiCtxBuilder::new()
63 | .set_pre_open_path(vec!["/"], vec![])
64 | .set_env_vars(envs.iter().map(String::as_str).collect())
65 | .set_arguments(args.iter().map(String::as_str).collect())
66 | .build();
67 |
68 | module.set_wasi_context(wasi_ctx);
69 |
70 | // TODO: no way to register a named module with bytes?
71 |
72 | log::info!("Create a WAMR instance");
73 |
74 | let instance = WamrInst::new(&self.runtime, &module, 1024 * 64)
75 | .context("Failed to create instance")?;
76 |
77 | log::info!("Running {func:?}");
78 | let function =
79 | Function::find_export_func(&instance, &func).context("Failed to find function")?;
80 | let status = function
81 | .call(&instance, &vec![])
82 | .map(|_| 0)
83 | .map_err(|err| {
84 | log::error!("Error: {err:?}");
85 | err
86 | })
87 | .context("Failed to call function")?;
88 |
89 | Ok(status)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wamr/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[cfg(unix)]
2 | pub mod instance;
3 |
4 | #[cfg(unix)]
5 | pub use instance::WamrShim;
6 |
7 | #[cfg(unix)]
8 | #[cfg(test)]
9 | #[path = "tests.rs"]
10 | mod wamr_tests;
11 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wamr/src/main.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(target_os = "windows"))]
2 | use containerd_shim_wamr::WamrShim;
3 | use containerd_shim_wasm::shim::Cli;
4 |
5 | #[cfg(target_os = "windows")]
6 | fn main() {
7 | panic!("WAMR shim is not supported on Windows");
8 | }
9 |
10 | #[cfg(not(target_os = "windows"))]
11 | fn main() {
12 | WamrShim::run(None);
13 | }
14 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wamr/src/tests.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 |
3 | use containerd_shim_wasm::testing::WasiTest;
4 | use containerd_shim_wasm::testing::modules::*;
5 | use serial_test::serial;
6 |
7 | use crate::WamrShim as WasiEngine;
8 |
9 | #[test]
10 | #[serial]
11 | fn test_delete_after_create() -> anyhow::Result<()> {
12 | WasiTest::::builder()?.build()?.delete()?;
13 | Ok(())
14 | }
15 |
16 | #[test]
17 | #[serial]
18 | fn test_hello_world() -> anyhow::Result<()> {
19 | let (exit_code, stdout, _) = WasiTest::::builder()?
20 | .with_wasm(HELLO_WORLD)?
21 | .build()?
22 | .start()?
23 | .wait(Duration::from_secs(10))?;
24 |
25 | assert_eq!(exit_code, 0);
26 | assert_eq!(stdout, "hello world\n");
27 |
28 | Ok(())
29 | }
30 |
31 | #[test]
32 | #[serial]
33 | fn test_hello_world_oci() -> anyhow::Result<()> {
34 | let (builder, _oci_cleanup) = WasiTest::::builder()?
35 | .with_wasm(HELLO_WORLD)?
36 | .as_oci_image(None, None)?;
37 |
38 | let (exit_code, stdout, _) = builder.build()?.start()?.wait(Duration::from_secs(10))?;
39 |
40 | assert_eq!(exit_code, 0);
41 | assert_eq!(stdout, "hello world\n");
42 |
43 | Ok(())
44 | }
45 | #[test]
46 | #[serial]
47 | fn test_unreachable() -> anyhow::Result<()> {
48 | let (exit_code, _, _) = WasiTest::::builder()?
49 | .with_wasm(UNREACHABLE)?
50 | .build()?
51 | .start()?
52 | .wait(Duration::from_secs(10))?;
53 |
54 | assert_ne!(exit_code, 0);
55 |
56 | Ok(())
57 | }
58 |
59 | #[test]
60 | #[serial]
61 | fn test_seccomp() -> anyhow::Result<()> {
62 | let (exit_code, stdout, _) = WasiTest::::builder()?
63 | .with_wasm(SECCOMP)?
64 | .build()?
65 | .start()?
66 | .wait(Duration::from_secs(10))?;
67 |
68 | assert_eq!(exit_code, 0);
69 | assert_eq!(stdout.trim(), "current working dir: /");
70 |
71 | Ok(())
72 | }
73 |
74 | #[test]
75 | #[serial]
76 | fn test_has_default_devices() -> anyhow::Result<()> {
77 | let (exit_code, _, _) = WasiTest::::builder()?
78 | .with_wasm(HAS_DEFAULT_DEVICES)?
79 | .build()?
80 | .start()?
81 | .wait(Duration::from_secs(10))?;
82 |
83 | assert_eq!(exit_code, 0);
84 |
85 | Ok(())
86 | }
87 |
88 | #[test]
89 | #[ignore = "disabled because the WAMR SDK doesn't expose exit code yet"]
90 | // See https://github.com/containerd/runwasi/pull/716#discussion_r1827086060
91 | fn test_exit_code() -> anyhow::Result<()> {
92 | let (exit_code, _, _) = WasiTest::::builder()?
93 | .with_wasm(EXIT_CODE)?
94 | .build()?
95 | .start()?
96 | .wait(Duration::from_secs(10))?;
97 |
98 | assert_eq!(exit_code, 42);
99 |
100 | Ok(())
101 | }
102 |
103 | #[test]
104 | #[ignore]
105 | // See https://github.com/containerd/runwasi/pull/716#issuecomment-2458200081
106 | fn test_custom_entrypoint() -> anyhow::Result<()> {
107 | let (exit_code, stdout, _) = WasiTest::::builder()?
108 | .with_start_fn("foo")
109 | .with_wasm(CUSTOM_ENTRYPOINT)?
110 | .build()?
111 | .start()?
112 | .wait(Duration::from_secs(10))?;
113 |
114 | assert_eq!(exit_code, 0);
115 | assert_eq!(stdout, "hello world\n");
116 |
117 | Ok(())
118 | }
119 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "containerd-shim-wasm-test-modules"
3 | description = "Set of WebAssembly modules for testing containerd shims for wasm"
4 | version = "0.4.1"
5 | edition.workspace = true
6 | license.workspace = true
7 |
8 | [build-dependencies]
9 | anyhow = { workspace = true }
10 | lazy_static = "1.5.0"
11 | wat = { workspace = true }
12 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/build.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 | use std::path::{Path, PathBuf};
3 | use std::process::Command;
4 |
5 | use anyhow::{Context, Result, bail};
6 | use lazy_static::lazy_static;
7 |
8 | fn env_path(key: impl AsRef) -> Result {
9 | std::env::var_os(key.as_ref())
10 | .map(Into::into)
11 | .with_context(|| format!("failed to read env-var {}", key.as_ref()))
12 | }
13 |
14 | lazy_static! {
15 | static ref OUT_DIR: PathBuf = env_path("OUT_DIR").unwrap();
16 | static ref PKG_DIR: PathBuf = env_path("CARGO_MANIFEST_DIR").unwrap();
17 | }
18 |
19 | fn main() -> Result<()> {
20 | let modules_file = OUT_DIR.join("modules.rs");
21 | let modules_dir = PKG_DIR.join("src").join("modules");
22 |
23 | let mut writer = std::fs::File::create(modules_file)?;
24 |
25 | let paths = std::fs::read_dir(modules_dir)?;
26 | for entry in paths.flatten() {
27 | let src = entry.path();
28 | let name = path_to_ident(&src)?.to_ascii_uppercase();
29 |
30 | println!("cargo:rerun-if-changed={}", src.to_string_lossy());
31 |
32 | writeln!(writer, "pub const {name}: TestModule = TestModule {{")?;
33 | let dst = match src
34 | .extension()
35 | .unwrap_or_default()
36 | .to_str()
37 | .unwrap_or_default()
38 | {
39 | "rs" => {
40 | writeln!(writer, " source: Some(include_str!({src:?})),")?;
41 | compile_rust(&src)?
42 | }
43 | "wat" => {
44 | writeln!(writer, " source: Some(include_str!({src:?})),")?;
45 | compile_wat(&src)?
46 | }
47 | "wasm" => {
48 | writeln!(writer, " source: None,")?;
49 | move_wasm(&src)?
50 | }
51 | _ => bail!("unrecognized file format for source file {src:?}"),
52 | };
53 |
54 | writeln!(writer, " bytes: include_bytes!({dst:?}),")?;
55 | writeln!(writer, "}};")?;
56 | }
57 |
58 | Ok(())
59 | }
60 |
61 | fn compile_rust(src: impl AsRef) -> Result {
62 | let rustc = std::env::var_os("RUSTC").context("reading RUSTC")?;
63 | let src = src.as_ref();
64 | let dst = output_for(src)?;
65 |
66 | Command::new(rustc)
67 | .arg("--target=wasm32-wasip1")
68 | .arg("-Copt-level=z")
69 | .arg("-Cstrip=symbols")
70 | .arg("-o")
71 | .arg(&dst)
72 | .arg(src)
73 | .spawn()?
74 | .wait()?
75 | .success()
76 | .then_some(dst)
77 | .context("running rustc")
78 | }
79 |
80 | fn compile_wat(src: impl AsRef) -> Result {
81 | let src = src.as_ref();
82 | let dst = output_for(src)?;
83 |
84 | let bytes = wat::parse_file(src)?;
85 | std::fs::write(&dst, bytes)?;
86 |
87 | Ok(dst)
88 | }
89 |
90 | fn move_wasm(src: impl AsRef) -> Result {
91 | let src = src.as_ref();
92 | let dst = output_for(src)?;
93 |
94 | std::fs::copy(src, &dst)?;
95 |
96 | Ok(dst)
97 | }
98 |
99 | fn output_for(src: impl AsRef) -> Result {
100 | let src = src.as_ref();
101 | let filename = src
102 | .file_name()
103 | .with_context(|| format!("getting filename of {src:?}"))?;
104 | Ok(OUT_DIR.join(filename).with_extension("wasm"))
105 | }
106 |
107 | fn path_to_ident(path: impl AsRef) -> Result {
108 | let path = path.as_ref();
109 | let ident: String = path
110 | .file_stem()
111 | .with_context(|| format!("getting filename of {path:?}"))?
112 | .to_str()
113 | .context("converting filename to string")?
114 | .chars()
115 | .map(|c| match c {
116 | 'A'..='Z' | 'a'..='z' | '0'..='9' => c,
117 | _ => '_',
118 | })
119 | .collect();
120 |
121 | if !ident.starts_with(char::is_alphabetic) {
122 | bail!("please start the filename with [a-zA-Z]")
123 | }
124 |
125 | Ok(ident)
126 | }
127 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub struct TestModule {
2 | pub source: Option<&'static str>,
3 | pub bytes: &'static [u8],
4 | }
5 |
6 | impl AsRef<[u8]> for TestModule {
7 | fn as_ref(&self) -> &[u8] {
8 | self.bytes
9 | }
10 | }
11 |
12 | include!(concat!(env!("OUT_DIR"), "/modules.rs"));
13 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/component-hello-world.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/component-hello-world.wasm
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/custom_entrypoint.wat:
--------------------------------------------------------------------------------
1 | (module
2 | ;; Import the required fd_write WASI function which will write the given io vectors to stdout
3 | ;; The function signature for fd_write is:
4 | ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
5 | (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
6 |
7 | (memory 1)
8 | (export "memory" (memory 0))
9 |
10 | ;; Write 'hello world\n' to memory at an offset of 8 bytes
11 | ;; Note the trailing newline which is required for the text to appear
12 | (data (i32.const 8) "hello world\n")
13 |
14 | (func $main (export "foo")
15 | ;; Creating a new io vector within linear memory
16 | (i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
17 | (i32.store (i32.const 4) (i32.const 12)) ;; iov.iov_len - The length of the 'hello world\n' string
18 |
19 | (call $fd_write
20 | (i32.const 1) ;; file_descriptor - 1 for stdout
21 | (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
22 | (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
23 | (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
24 | )
25 | drop ;; Discard the number of bytes written from the top of the stack
26 | )
27 | )
28 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/exit_code.wat:
--------------------------------------------------------------------------------
1 | (module
2 | ;; Import the required proc_exit WASI function which terminates the program with an exit code.
3 | ;; The function signature for proc_exit is:
4 | ;; (exit_code: i32) -> !
5 | (import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32)))
6 | (memory 1)
7 | (export "memory" (memory 0))
8 | (func $main (export "_start")
9 | (call $proc_exit (i32.const 42))
10 | unreachable
11 | )
12 | )
13 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/fetch_rs.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/fetch_rs.wasm
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/has_default_devices.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | fn main() {
4 | // Runtime must supply at least the following files regardless of OCI devices setting:
5 | let devices = [
6 | "/dev/null",
7 | "/dev/zero",
8 | "/dev/full",
9 | "/dev/random",
10 | "/dev/urandom",
11 | "/dev/tty",
12 | ];
13 |
14 | for device in devices.iter() {
15 | if Path::new(device).exists() {
16 | println!("{device} found");
17 | } else {
18 | panic!("{device} not found");
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http.wasm
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http_csharp.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http_csharp.wasm
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/hello_world.wat:
--------------------------------------------------------------------------------
1 | (module
2 | ;; Import the required fd_write WASI function which will write the given io vectors to stdout
3 | ;; The function signature for fd_write is:
4 | ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
5 | (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
6 |
7 | (memory 1)
8 | (export "memory" (memory 0))
9 |
10 | ;; Write 'hello world\n' to memory at an offset of 8 bytes
11 | ;; Note the trailing newline which is required for the text to appear
12 | (data (i32.const 8) "hello world\n")
13 |
14 | (func $main (export "_start")
15 | ;; Creating a new io vector within linear memory
16 | (i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
17 | (i32.store (i32.const 4) (i32.const 12)) ;; iov.iov_len - The length of the 'hello world\n' string
18 |
19 | (call $fd_write
20 | (i32.const 1) ;; file_descriptor - 1 for stdout
21 | (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
22 | (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
23 | (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
24 | )
25 | drop ;; Discard the number of bytes written from the top of the stack
26 | )
27 | )
28 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/seccomp.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | // Add current working dir request so that we have some known system call to
3 | // test seccomp with.
4 | let cwd = std::env::current_dir().unwrap();
5 |
6 | println!(
7 | "current working dir: {}",
8 | cwd.to_string_lossy()
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/simple_component.wat:
--------------------------------------------------------------------------------
1 | (component
2 | (core module $m
3 | (func (export "thunk"))
4 | (func (export "thunk-trap") unreachable)
5 | )
6 | (core instance $i (instantiate $m))
7 | (func (export "thunk")
8 | (canon lift (core func $i "thunk"))
9 | )
10 | (func (export "thunk-trap")
11 | (canon lift (core func $i "thunk-trap"))
12 | )
13 | )
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm-test-modules/src/modules/unreachable.wat:
--------------------------------------------------------------------------------
1 | (func $main (export "_start")
2 | (unreachable)
3 | )
4 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "containerd-shim-wasm"
3 | description = "Library for building containerd shims for wasm"
4 | version = "1.0.0"
5 | edition.workspace = true
6 | license.workspace = true
7 | readme = "README.md"
8 | homepage.workspace = true
9 | repository.workspace = true
10 |
11 | [lib]
12 | doctest = false
13 |
14 | [dependencies]
15 | anyhow = { workspace = true }
16 | chrono = { workspace = true }
17 | containerd-shimkit = { workspace = true }
18 | containerd-shim = { workspace = true }
19 | containerd-shim-wasm-test-modules = { workspace = true, optional = true }
20 | oci-tar-builder = { workspace = true, optional = true }
21 | env_logger = { workspace = true, optional = true }
22 | libc = { workspace = true }
23 | log = { workspace = true }
24 | oci-spec = { workspace = true }
25 | serde = { workspace = true }
26 | serde_json = { workspace = true }
27 | tempfile = { workspace = true, optional = true }
28 | wat = { workspace = true }
29 | tokio = { workspace = true, features = ["full"] }
30 | futures = { version = "0.3.30" }
31 | wasmparser = { version = "0.231.0" }
32 | tokio-stream = { version = "0.1" }
33 | sha256 = { workspace = true }
34 | serde_bytes = "0.11"
35 | tokio-async-drop = "0.1"
36 | trait-variant = "0.1"
37 |
38 | # tracing
39 | # note: it's important to keep the version of tracing in sync with containerd-shimkit's tracing-subscriber
40 | tracing = { workspace = true, optional = true }
41 |
42 | [target.'cfg(unix)'.dependencies]
43 | caps = "0.5"
44 | # this must match the version pulled by libcontainer
45 | dbus = { version = "0", features = ["vendored"] }
46 | libcontainer = { workspace = true, features = [
47 | "libseccomp",
48 | "systemd",
49 | "v1",
50 | "v2",
51 | ] }
52 | nix = { workspace = true, features = ["sched", "mount"] }
53 | containerd-client = "0.6.0"
54 |
55 | [target.'cfg(windows)'.dependencies]
56 | windows-sys = { workspace = true, features = [
57 | "Win32_Foundation",
58 | "Win32_Storage_FileSystem",
59 | ] }
60 | # vendored code
61 | mio = { version = "1.0", features = ["os-ext", "os-poll"] }
62 |
63 | [build-dependencies]
64 | ttrpc-codegen = { version = "0.4.2" }
65 |
66 | [dev-dependencies]
67 | tokio = { workspace = true, features = ["signal"] }
68 | containerd-shim-wasm-test-modules = { workspace = true }
69 | env_logger = { workspace = true }
70 | tempfile = { workspace = true }
71 | oci-tar-builder = { workspace = true }
72 | rand = "0.9"
73 | temp-env = "0.3"
74 | ctor = "0.4.2"
75 |
76 | [features]
77 | testing = [
78 | "dep:containerd-shim-wasm-test-modules",
79 | "dep:env_logger",
80 | "dep:tempfile",
81 | "dep:oci-tar-builder",
82 | ]
83 | opentelemetry = ["containerd-shimkit/opentelemetry"]
84 | tracing = ["dep:tracing", "containerd-shimkit/tracing"]
85 |
86 | [package.metadata.cargo-machete]
87 | # used as part of a derive macro
88 | ignored = ["serde_bytes"]
89 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # containerd-shim-wasm
4 |
5 | A library to help build containerd shims for Wasm workloads.
6 |
7 | ## Usage
8 |
9 | To implement a shim, simply implement the `Shim` and `Sandbox` trait:
10 |
11 | ```rust,no_run
12 | use containerd_shim_wasm::{
13 | shim::{Shim, Config, Cli, Version, version},
14 | sandbox::Sandbox,
15 | sandbox::context::RuntimeContext,
16 | };
17 | use anyhow::Result;
18 |
19 | struct MyShim;
20 |
21 | #[derive(Default)]
22 | struct MySandbox;
23 |
24 | impl Shim for MyShim {
25 | type Sandbox = MySandbox;
26 |
27 | fn name() -> &'static str {
28 | "my-shim"
29 | }
30 |
31 | fn version() -> Version {
32 | version!()
33 | }
34 | }
35 |
36 | impl Sandbox for MySandbox {
37 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result {
38 | // Implement your Wasm runtime logic here
39 | Ok(0)
40 | }
41 | }
42 |
43 | MyShim::run(None);
44 | ```
45 |
46 | The `Engine` trait provides optional methods you can override:
47 |
48 | - `can_handle()` - Validates that the runtime can run the container (checks Wasm file headers by default)
49 | - `supported_layers_types()` - Returns supported OCI layer types
50 | - `precompile()` - Allows precompilation of Wasm modules
51 | - `can_precompile()` - Indicates if the runtime supports precompilation
52 |
53 | The resulting shim uses [Youki](https://github.com/youki-dev/youki)'s `libcontainer` crate to manage the container lifecycle, such as creating the container, starting it, and deleting it, and youki handles container sandbox for you.
54 |
55 | ### Running the shim
56 |
57 | containerd expects the shim binary to be installed into `$PATH` (as seen by the containerd process) with a binary name like `containerd-shim-myshim-v1` which maps to the `io.containerd.myshim.v1` runtime. It can be [configured in containerd](https://github.com/containerd/containerd/blob/main/core/runtime/v2/README.md#configuring-runtimes).
58 |
59 | This crate is not tied to any specific wasm engine.
60 |
61 | Check out these projects that build on top of runwasi:
62 | - [spinframework/containerd-shim-spin](https://github.com/spinframework/containerd-shim-spin)
63 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/src/containerd/lease.rs:
--------------------------------------------------------------------------------
1 | #![cfg(unix)]
2 |
3 | use std::mem::ManuallyDrop;
4 |
5 | use anyhow::Context as _;
6 | use containerd_client::services::v1::DeleteRequest;
7 | use containerd_client::services::v1::leases_client::LeasesClient;
8 | use containerd_client::tonic::transport::Channel;
9 | use containerd_client::{tonic, with_namespace};
10 | use tokio_async_drop::tokio_async_drop;
11 | use tonic::Request;
12 |
13 | #[derive(Debug)]
14 | pub(crate) struct LeaseGuard {
15 | inner: Option,
16 | }
17 |
18 | #[derive(Debug)]
19 | pub(crate) struct LeaseGuardInner {
20 | client: LeasesClient,
21 | req: tonic::Request,
22 | }
23 |
24 | impl LeaseGuard {
25 | pub fn new(
26 | client: LeasesClient,
27 | id: impl Into,
28 | namespace: impl AsRef,
29 | ) -> Self {
30 | let id = id.into();
31 | let req = DeleteRequest { id, sync: false };
32 | let req = with_namespace!(req, namespace.as_ref());
33 | let inner = Some(LeaseGuardInner { client, req });
34 | Self { inner }
35 | }
36 |
37 | // Release a LeaseGuard in a way that we can await for it to complete.
38 | // The alternative to `release` is to `drop` the LeaseGuard, but in that case we can't await for its completion.
39 | pub async fn release(self) -> anyhow::Result<()> {
40 | let mut this = ManuallyDrop::new(self);
41 | this.inner.take().unwrap().release().await?;
42 | Ok(())
43 | }
44 |
45 | pub fn id(&self) -> &'_ str {
46 | &self.inner.as_ref().unwrap().req.get_ref().id
47 | }
48 | }
49 |
50 | impl LeaseGuardInner {
51 | async fn release(mut self) -> anyhow::Result<()> {
52 | self.client
53 | .delete(self.req)
54 | .await
55 | .context("Failed to remove lease")?;
56 | Ok(())
57 | }
58 | }
59 |
60 | // Provides a best effort for dropping a lease of the content. If the lease cannot be dropped, it will log a warning
61 | impl Drop for LeaseGuard {
62 | fn drop(&mut self) {
63 | let inner = self.inner.take().unwrap();
64 | tokio_async_drop!({
65 | match inner.release().await {
66 | Ok(()) => log::info!("removed lease"),
67 | Err(err) => log::warn!("error removing lease: {err}"),
68 | }
69 | });
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/src/containerd/mod.rs:
--------------------------------------------------------------------------------
1 | #![cfg(unix)]
2 |
3 | mod client;
4 | mod lease;
5 |
6 | pub(crate) use client::Client;
7 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc = include_str!("../README.md")]
2 | #![doc(
3 | html_logo_url = "https://raw.githubusercontent.com/containerd/runwasi/e251de3307bbdc8bf3229020ea2ae2711f31aafa/art/logo/runwasi_logo_icon.svg"
4 | )]
5 |
6 | pub mod sandbox;
7 | pub mod shim;
8 |
9 | #[cfg_attr(unix, path = "sys/unix/mod.rs")]
10 | #[cfg_attr(windows, path = "sys/windows/mod.rs")]
11 | pub(crate) mod sys;
12 |
13 | #[cfg(any(test, feature = "testing"))]
14 | /// Utilities for writing shims tests.
15 | /// You can use this to test your runwasi based shim.
16 | pub mod testing;
17 |
18 | #[cfg(test)]
19 | /// Tests for runwasi's containerd-shim-wasm.
20 | mod test;
21 |
22 | pub(crate) mod containerd;
23 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/src/sandbox/mod.rs:
--------------------------------------------------------------------------------
1 | use std::fs::File;
2 | use std::io::Read;
3 |
4 | use anyhow::{Context, Result};
5 | use context::{RuntimeContext, Source};
6 | use path::PathResolve as _;
7 |
8 | pub mod context;
9 | pub(crate) mod path;
10 |
11 | #[trait_variant::make(Send)]
12 | pub trait Sandbox: Default + 'static {
13 | /// Run a WebAssembly container
14 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result;
15 |
16 | /// Check that the runtime can run the container.
17 | /// This checks runs after the container creation and before the container starts.
18 | /// By default it checks that the wasi_entrypoint is either:
19 | /// * a OCI image with wasm layers
20 | /// * a file with the `wasm` filetype header
21 | /// * a parsable `wat` file.
22 | async fn can_handle(&self, ctx: &impl RuntimeContext) -> Result<()> {
23 | // this async block is required to make the rewrite of trait_variant happy
24 | async move {
25 | let source = ctx.entrypoint().source;
26 |
27 | let path = match source {
28 | Source::File(path) => path,
29 | Source::Oci(_) => return Ok(()),
30 | };
31 |
32 | path.resolve_in_path_or_cwd()
33 | .next()
34 | .context("module not found")?;
35 |
36 | let mut buffer = [0; 4];
37 | File::open(&path)?.read_exact(&mut buffer)?;
38 |
39 | if buffer.as_slice() != b"\0asm" {
40 | // Check if this is a `.wat` file
41 | wat::parse_file(&path)?;
42 | }
43 |
44 | Ok(())
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/src/sandbox/path.rs:
--------------------------------------------------------------------------------
1 | use std::path::{Path, PathBuf};
2 |
3 | /// PathResolve allows to resolve a file path in a set of directories.
4 | pub(crate) trait PathResolve {
5 | /// Resolve the path of a file give a set of directories as the `which` unix
6 | /// command would do with components of the `PATH` environment variable, and
7 | /// return an iterator over all candidates.
8 | /// Resulting candidates are files that exist, but no other constraint is
9 | /// imposed, in particular this function does not check for the executable bits.
10 | /// Further constraints can be added by calling filtering the returned iterator.
11 | fn resolve_in_dirs(
12 | &self,
13 | dirs: impl IntoIterator- >,
14 | ) -> impl Iterator
- ;
15 | /// Like `resolve_in_dirs`, but searches on the entries of `PATH`.
16 | fn resolve_in_path(&self) -> impl Iterator
- ;
17 | /// Like `resolve_in_dirs`, but searches on the entries of `PATH`, and on `cwd`, in that order.
18 | fn resolve_in_path_or_cwd(&self) -> impl Iterator
- ;
19 | }
20 |
21 | /// Gets the content of the `PATH` environment variable as an iterator over its components
22 | pub(crate) fn paths() -> impl Iterator
- {
23 | std::env::var_os("PATH")
24 | .as_ref()
25 | .map(std::env::split_paths)
26 | .into_iter()
27 | .flatten()
28 | .collect::>()
29 | .into_iter()
30 | }
31 |
32 | impl> PathResolve for T {
33 | fn resolve_in_dirs(
34 | &self,
35 | dirs: impl IntoIterator
- >,
36 | ) -> impl Iterator
- {
37 | let cwd = std::env::current_dir().ok();
38 |
39 | let has_separator = self.as_ref().components().count() > 1;
40 |
41 | // The seemingly extra complexity here is because we can only have one concrete
42 | // return type even if we return an `impl Iterator
- `
43 | let (first, second) = if has_separator {
44 | // file has a separator, we only need to rºesolve relative to `cwd`, we must ignore `PATH`
45 | (cwd, None)
46 | } else {
47 | // file is just a binary name, we must not resolve relative to `cwd`, but relative to `PATH` components
48 | let dirs = dirs.into_iter().filter_map(move |p| {
49 | let path = cwd.as_ref()?.join(p.as_ref()).canonicalize().ok()?;
50 | path.is_dir().then_some(path)
51 | });
52 | (None, Some(dirs))
53 | };
54 |
55 | let file = self.as_ref().to_owned();
56 | first
57 | .into_iter()
58 | .chain(second.into_iter().flatten())
59 | .filter_map(move |p| {
60 | // skip any paths that are not files
61 | let path = p.join(&file).canonicalize().ok()?;
62 | path.is_file().then_some(path)
63 | })
64 | }
65 |
66 | // Like `find_in_dirs`, but searches on the entries of `PATH`.
67 | fn resolve_in_path(&self) -> impl Iterator
- {
68 | self.resolve_in_dirs(paths())
69 | }
70 |
71 | // Like `find_in_dirs`, but searches on the entries of `PATH`, and on `cwd`, in that order.
72 | fn resolve_in_path_or_cwd(&self) -> impl Iterator
- {
73 | self.resolve_in_dirs(paths().chain(std::env::current_dir().ok()))
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/crates/containerd-shim-wasm/src/shim/cli.rs:
--------------------------------------------------------------------------------
1 | //! Command line interface for the containerd shim.
2 | //!
3 | //! The CLI provides the interface between containerd and the Wasm runtime.
4 | //! It handles commands like start and delete from containerd's shim API.
5 | //!
6 | //! ## Usage
7 | //!
8 | //! The shim binary should be named `containerd-shim--v1` and installed in $PATH.
9 | //! containerd will call the shim with various commands.
10 | //!
11 | //! ## Configuration
12 | //!
13 | //! The shim can be configured using the [`Config`] struct:
14 | //!
15 | //! ```rust, no_run
16 | //! use containerd_shim_wasm::shim::Config;
17 | //!
18 | //! let config = Config {
19 | //! // Disable automatic logger setup
20 | //! no_setup_logger: false,
21 | //! // Set default log level
22 | //! default_log_level: "info".to_string(),
23 | //! };
24 | //! ```
25 | //!
26 | //! ## Example usage:
27 | //!
28 | //! ```rust, no_run
29 | //! use containerd_shim_wasm::{
30 | //! shim::{Shim, Cli, Config, Version, version},
31 | //! sandbox::Sandbox,
32 | //! sandbox::context::RuntimeContext,
33 | //! };
34 | //! use anyhow::Result;
35 | //!
36 | //! struct MyShim;
37 | //!
38 | //! #[derive(Default)]
39 | //! struct MySandbox;
40 | //!
41 | //! impl Shim for MyShim {
42 | //! type Sandbox = MySandbox;
43 | //!
44 | //! fn name() -> &'static str {
45 | //! "my-shim"
46 | //! }
47 | //!
48 | //! fn version() -> Version {
49 | //! version!()
50 | //! }
51 | //! }
52 | //!
53 | //! impl Sandbox for MySandbox {
54 | //! async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result {
55 | //! Ok(0)
56 | //! }
57 | //! }
58 | //!
59 | //! let config = Config {
60 | //! default_log_level: "error".to_string(),
61 | //! ..Default::default()
62 | //! };
63 | //!
64 | //! MyShim::run(config);
65 | //! ```
66 | //!
67 | //! When the `opentelemetry` feature is enabled, additional runtime config
68 | //! is available through environment variables:
69 | //!
70 | //! - `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`: Enable OpenTelemetry tracing
71 | //! - `OTEL_EXPORTER_OTLP_ENDPOINT`: Enable OpenTelemetry tracing as above
72 | //! - `OTEL_SDK_DISABLED`: Disable OpenTelemetry SDK
73 | //!
74 |
75 | use crate::shim::{Config, Instance, Shim};
76 |
77 | mod private {
78 | pub trait Sealed {}
79 | }
80 |
81 | impl private::Sealed for S {}
82 |
83 | pub trait Cli: Shim + private::Sealed {
84 | /// Main entry point for the shim.
85 | ///
86 | /// If the `opentelemetry` feature is enabled, this function will start the shim with OpenTelemetry tracing.
87 | ///
88 | /// It parses OTLP configuration from the environment and initializes the OpenTelemetry SDK.
89 | fn run(config: impl Into