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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ version: 2
before:
hooks:
- go mod tidy
- ./scripts/package-managed-runtime-bundle.sh
- "sed -i.bak 's/\"version\": \".*\"/\"version\": \"{{ .Version }}\"/' plugin.json && rm -f plugin.json.bak"

builds:
Expand All @@ -28,6 +29,8 @@ archives:
- plugin.json
- plugin.contracts.json
- runtime-adapters.json
- managed-runtime-bundles.json
- .release/managed-runtime-bundles/*
- LICENSE

checksum:
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ registry allowlists, proof/reward mutation, and worker binding.
Runtime adapter metadata is published in `runtime-adapters.json` and referenced
from `plugin.json` through `runtimeAdaptersRef`.

Managed runtime bundle metadata is published in `managed-runtime-bundles.json`
and referenced from `plugin.json` through `managedRuntimeBundlesRef`.

## Runtime Backend Probes

The `container` package can probe Docker-compatible runtimes through
Expand All @@ -29,6 +32,23 @@ reports are only emitted after the runtime executable is found, version probing
works, and the conformance command succeeds. Degraded and unsupported reports do
not advertise executor providers or executor refs.

The `ManagedContainerdRuntimeProbes` helper builds target-aware probes from the
managed runtime bundle catalog and an explicit agent-managed install root. The
initial managed bundle target is Linux amd64 and is pinned to
`containerd/nerdctl` `v2.3.1` `nerdctl-full-2.3.1-linux-amd64.tar.gz`. Agents
must only advertise this backend after the bundle descriptor validates for the
current OS/architecture, the pinned runtime has been downloaded and extracted
into the agent-managed install root, the helper resolves an absolute
`bin/nerdctl` path inside that root, and the same conformance checks pass. The
managed backend uses an opaque nerdctl namespace so workloads run outside the
host's default container namespace.

Managed runtime release packaging verifies the upstream `SHA256SUMS` and
`SHA256SUMS.asc` digests and emits a source manifest in the plugin release
archive. Agent-side download, extraction, signature verification, and lifecycle
wiring are intentionally left to the workflow-compute host integration phase so
the plugin does not advertise a host-installed `nerdctl` as bundled.

The default conformance image reference is the Debian 13 distroless static index:

```text
Expand Down
1 change: 1 addition & 0 deletions container/adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ type RuntimeBackendCatalogEntry struct {
BackendID string `json:"backend_id"`
Families []core.RuntimeBackendFamily `json:"families"`
Tools []core.ContainerRuntimeTool `json:"tools"`
SupportedTargets []core.ManagedRuntimeTarget `json:"supported_targets,omitempty"`
IsolationModes []core.RuntimeIsolationMode `json:"isolation_modes"`
InstallBurdens []core.RuntimeInstallBurden `json:"install_burdens"`
RuntimeProfiles []core.RuntimeProfile `json:"runtime_profiles"`
Expand Down
115 changes: 115 additions & 0 deletions container/managed_runtime_bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package container

import (
"errors"
"fmt"
"slices"
"strconv"
"strings"
"time"

core "github.com/GoCodeAlone/workflow-plugin-compute-core/protocol"
)

type ManagedRuntimeBundleCatalog struct {
ReleaseTag string `json:"release_tag,omitempty"`
SourceBaseURL string `json:"source_base_url,omitempty"`
GeneratedAt time.Time `json:"generated_at"`
Bundles []core.ManagedRuntimeBundleDescriptor `json:"bundles"`
BlockedVersions []string `json:"blocked_versions,omitempty"`
RevokedKeyIDs []string `json:"revoked_key_ids,omitempty"`
MinimumVersion string `json:"minimum_version,omitempty"`
StableSigningKey string `json:"stable_signing_key,omitempty"`
}

func (c ManagedRuntimeBundleCatalog) Bundle(bundleID string, now time.Time) (core.ManagedRuntimeBundleDescriptor, error) {
if strings.TrimSpace(bundleID) == "" {
return core.ManagedRuntimeBundleDescriptor{}, errors.New("bundle_id is required")
}
if now.IsZero() {
return core.ManagedRuntimeBundleDescriptor{}, errors.New("validation time is required")
}
for _, bundle := range c.Bundles {
if bundle.BundleID != bundleID {
continue
}
if err := validateManagedRuntimeBundlePolicy(c, bundle, now); err != nil {
return core.ManagedRuntimeBundleDescriptor{}, err
}
return bundle, nil
}
return core.ManagedRuntimeBundleDescriptor{}, fmt.Errorf("managed runtime bundle %q not found", bundleID)
}

func (c ManagedRuntimeBundleCatalog) BundleForTarget(bundleID, targetOS, targetArch string, now time.Time) (core.ManagedRuntimeBundleDescriptor, error) {
bundle, err := c.Bundle(bundleID, now)
if err != nil {
return core.ManagedRuntimeBundleDescriptor{}, err
}
if !managedRuntimeBundleSupportsTarget(bundle, targetOS, targetArch) {
return core.ManagedRuntimeBundleDescriptor{}, fmt.Errorf("managed runtime bundle %q does not support %s/%s", bundleID, targetOS, targetArch)
}
return bundle, nil
}

func validateManagedRuntimeBundlePolicy(c ManagedRuntimeBundleCatalog, bundle core.ManagedRuntimeBundleDescriptor, now time.Time) error {
if err := bundle.ValidateAt(now); err != nil {
return err
}
if slices.Contains(c.BlockedVersions, bundle.Version) ||
slices.Contains(bundle.UpdatePolicy.BlockedVersions, bundle.Version) ||
slices.Contains(bundle.CVEPolicy.BlockedVersions, bundle.Version) {
return fmt.Errorf("managed runtime bundle version %q is blocked", bundle.Version)
}
if slices.Contains(c.RevokedKeyIDs, bundle.SignatureKeyID) ||
slices.Contains(bundle.CVEPolicy.RevokedKeyIDs, bundle.SignatureKeyID) {
return fmt.Errorf("managed runtime bundle signing key %q is revoked", bundle.SignatureKeyID)
}
minVersion := firstNonEmpty(c.MinimumVersion, bundle.UpdatePolicy.MinSupportedVersion)
if minVersion != "" && semverLess(bundle.Version, minVersion) {
return fmt.Errorf("managed runtime bundle version %q is below minimum %q", bundle.Version, minVersion)
}
if c.StableSigningKey != "" && bundle.SignatureKeyID != c.StableSigningKey {
return fmt.Errorf("managed runtime bundle signing key %q does not match stable signing key", bundle.SignatureKeyID)
}
return nil
}

func managedRuntimeBundleSupportsTarget(bundle core.ManagedRuntimeBundleDescriptor, targetOS, targetArch string) bool {
targetOS = strings.TrimSpace(targetOS)
targetArch = strings.TrimSpace(targetArch)
if targetOS == "" || targetArch == "" {
return false
}
for _, target := range bundle.SupportedTargets {
if target.OS == targetOS && target.Arch == targetArch {
return true
}
}
return false
}

func semverLess(left, right string) bool {
l := parseVersionParts(left)
r := parseVersionParts(right)
for i := range 3 {
if l[i] != r[i] {
return l[i] < r[i]
}
}
return false
}

func parseVersionParts(version string) [3]int {
version = strings.TrimPrefix(strings.TrimSpace(version), "v")
parts := strings.Split(version, ".")
var out [3]int
for i := 0; i < len(parts) && i < 3; i++ {
value, err := strconv.Atoi(parts[i])
if err != nil {
return [3]int{}
}
out[i] = value
}
return out
}
141 changes: 137 additions & 4 deletions container/runtime_probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ type RuntimeBackendProbeOptions struct {
ConformanceImage string
ConformanceCommand []string
ConformanceWorkspace string
RuntimeScopeArgs []string
IsolationMode core.RuntimeIsolationMode
InstallBurden core.RuntimeInstallBurden
RuntimeProfiles []core.RuntimeProfile
ConformanceProfiles []string
ManagedBundle *core.ManagedRuntimeBundleDescriptor
GeneratedAt time.Time
}

Expand All @@ -75,6 +77,28 @@ type RuntimeBackendProbe struct {
Runner RuntimeCommandRunner
}

type ManagedRuntimeInstallation struct {
Bundle core.ManagedRuntimeBundleDescriptor
Root string
CommandPath string
}

func NewManagedContainerdRuntimeInstallation(bundle core.ManagedRuntimeBundleDescriptor, root string) (ManagedRuntimeInstallation, error) {
root = strings.TrimSpace(root)
if root == "" {
return ManagedRuntimeInstallation{}, errors.New("managed runtime root is required")
}
root, err := filepath.Abs(root)
if err != nil {
return ManagedRuntimeInstallation{}, err
}
return ManagedRuntimeInstallation{
Bundle: bundle,
Root: root,
CommandPath: filepath.Join(root, "bin", "nerdctl"),
}, nil
}

func DockerCompatibleRuntimeProbes(runner RuntimeCommandRunner, generatedAt time.Time) []RuntimeBackendProbe {
if runner == nil {
runner = ExecRuntimeCommandRunner{}
Expand Down Expand Up @@ -135,6 +159,77 @@ func DockerCompatibleRuntimeProbes(runner RuntimeCommandRunner, generatedAt time
}
}

func ManagedContainerdRuntimeProbe(installation *ManagedRuntimeInstallation, runner RuntimeCommandRunner, generatedAt time.Time) RuntimeBackendProbe {
if runner == nil {
runner = ExecRuntimeCommandRunner{}
}
profiles := []core.RuntimeProfile{core.RuntimeProfileSandboxedOCI, core.RuntimeProfileContainerBuild}
backendID := "managed-containerd"
command := ""
var bundle *core.ManagedRuntimeBundleDescriptor
if installation != nil {
bundle = &installation.Bundle
command = installation.CommandPath
}
if bundle != nil && bundle.BundleID != "" {
backendID = bundle.BundleID
}
profile := defaultConformanceProfile
if bundle != nil && bundle.ConformanceProfile != "" {
profile = bundle.ConformanceProfile
}
return RuntimeBackendProbe{
Options: RuntimeBackendProbeOptions{
BackendID: backendID,
Family: core.RuntimeBackendFamilyContainerd,
Tool: core.ContainerRuntimeNerdctl,
Command: command,
VersionArgs: []string{"version", "--format", "{{.Client.Version}}"},
ConformanceImage: defaultRuntimeConformanceImage(),
ConformanceCommand: []string{defaultConformanceCommand},
RuntimeScopeArgs: ManagedRuntimeScopeArgs(backendID, profile),
IsolationMode: core.RuntimeIsolationUserNamespace,
InstallBurden: core.RuntimeInstallBundled,
RuntimeProfiles: profiles,
ConformanceProfiles: []string{profile},
ManagedBundle: bundle,
GeneratedAt: generatedAt,
},
Runner: runner,
}
}

func ManagedContainerdRuntimeProbes(catalog ManagedRuntimeBundleCatalog, installRoot string, runner RuntimeCommandRunner, generatedAt time.Time) []RuntimeBackendProbe {
if strings.TrimSpace(installRoot) == "" {
return nil
}
if generatedAt.IsZero() {
generatedAt = time.Now().UTC()
}
probes := make([]RuntimeBackendProbe, 0, len(catalog.Bundles))
for _, candidate := range catalog.Bundles {
bundle, err := catalog.BundleForTarget(candidate.BundleID, runtime.GOOS, runtime.GOARCH, generatedAt)
if err != nil {
continue
}
installation, err := NewManagedContainerdRuntimeInstallation(bundle, filepath.Join(installRoot, bundle.BundleID))
if err != nil {
continue
}
probes = append(probes, ManagedContainerdRuntimeProbe(&installation, runner, generatedAt))
}
return probes
}

func ManagedRuntimeScopeArgs(parts ...string) []string {
return []string{"--namespace", ManagedRuntimeScopeName(parts...)}
}

func ManagedRuntimeScopeName(parts ...string) string {
digest := digestForRuntimeClaim(append(parts, "scope")...)
return "wfcompute-" + digest[7:23]
}

func (p RuntimeBackendProbe) Probe(ctx context.Context) core.RuntimeBackendReport {
opts := p.Options.withDefaults()
runner := p.Runner
Expand All @@ -155,6 +250,35 @@ func (p RuntimeBackendProbe) Probe(ctx context.Context) core.RuntimeBackendRepor
ConformanceProfiles: slices.Clone(opts.ConformanceProfiles),
GeneratedAt: opts.GeneratedAt,
}
if opts.InstallBurden == core.RuntimeInstallBundled {
if opts.ManagedBundle == nil {
report.Reason = "managed runtime bundle is unavailable"
return report
}
if !filepath.IsAbs(opts.Command) {
report.Reason = "managed runtime executable is unavailable"
return report
}
if err := opts.ManagedBundle.ValidateAt(opts.GeneratedAt); err != nil {
report.Status = core.RuntimeBackendDegraded
report.Reason = redactRuntimeProbeDetail(fmt.Sprintf("managed runtime bundle validation failed: %v", err))
return report
}
report.Bundle = opts.ManagedBundle
report.Version = opts.ManagedBundle.Version
if opts.ManagedBundle.Family != "" {
report.Family = opts.ManagedBundle.Family
}
if opts.ManagedBundle.Tool != "" {
report.Tool = opts.ManagedBundle.Tool
}
if opts.ManagedBundle.OS != "" {
report.OS = opts.ManagedBundle.OS
}
if opts.ManagedBundle.Arch != "" {
report.Arch = opts.ManagedBundle.Arch
}
}
if _, err := runner.LookPath(opts.Command); err != nil {
report.Reason = "runtime executable is unavailable"
return report
Expand All @@ -165,24 +289,27 @@ func (p RuntimeBackendProbe) Probe(ctx context.Context) core.RuntimeBackendRepor
report.Reason = redactRuntimeProbeDetail(fmt.Sprintf("runtime version probe failed: %v", err))
return report
}
report.Version = sanitizeRuntimeVersion(string(versionResult.Stdout))
if report.Version == "" {
report.Version = sanitizeRuntimeVersion(string(versionResult.Stdout))
}
workspace, cleanup, err := runtimeBackendConformanceWorkspace(opts)
if err != nil {
report.Status = core.RuntimeBackendDegraded
report.Reason = redactRuntimeProbeDetail(fmt.Sprintf("runtime conformance workspace failed: %v", err))
return report
}
defer cleanup()
conformanceArgs := []string{
conformanceArgs := append([]string(nil), opts.RuntimeScopeArgs...)
conformanceArgs = append(conformanceArgs,
"run",
"--rm",
"--network", SandboxNetworkNone,
"-v", workspace + ":/workspace",
"-v", workspace+":/workspace",
"-w", "/workspace",
"-e", "WFCOMPUTE_RUNTIME_PROBE=1",
"--read-only",
opts.ConformanceImage,
}
)
conformanceArgs = append(conformanceArgs, opts.ConformanceCommand...)
conformanceResult, err := runner.Run(ctx, opts.Command, conformanceArgs...)
if err != nil {
Expand Down Expand Up @@ -334,6 +461,12 @@ func runtimeBackendConformanceEvidence(stdout []byte, report core.RuntimeBackend
if len(evidence.Details) == 0 {
evidence.Details = []string{firstNonEmpty(report.ConformanceProfiles...)}
}
if report.InstallBurden == core.RuntimeInstallBundled && report.Bundle != nil {
expectedScope := "runtime-scope:" + ManagedRuntimeScopeName(report.BackendID, firstNonEmpty(report.ConformanceProfiles...))
if !slices.Contains(evidence.Details, expectedScope) {
return core.RuntimeBackendEvidence{}, fmt.Errorf("missing managed runtime scope evidence")
}
}
return evidence, nil
}

Expand Down
Loading
Loading