diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2615381 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI +on: + push: + branches: [main] + pull_request: + branches: [main] +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - run: go build ./... + - run: go test ./... -v -race -count=1 + - run: go vet ./... + + wfctl-validate-contract: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - uses: GoCodeAlone/setup-wfctl@bcd880980f5bbe8d192d0c20ff6279d25331f956 + with: + version: v0.64.7 + - name: Validate plugin contract + run: wfctl plugin validate-contract . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..63dff36 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Release +on: + push: + tags: + - 'v*' +permissions: + contents: write +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - uses: GoCodeAlone/setup-wfctl@bcd880980f5bbe8d192d0c20ff6279d25331f956 + with: + version: v0.64.7 + - name: Validate plugin contract for publish + run: wfctl plugin validate-contract --for-publish --tag "${{ github.ref_name }}" . + - uses: goreleaser/goreleaser-action@v7 + with: + distribution: goreleaser + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Verify capabilities + run: | + RUNNER_ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') + BIN=$(jq -r --arg arch "$RUNNER_ARCH" \ + '[.[] | select(.type=="Binary" and .goos=="linux" and .goarch==$arch and (.name|startswith("workflow-plugin-compute-container")))] | .[0].path // ""' \ + dist/artifacts.json) + if [ -z "$BIN" ] || [ "$BIN" = "null" ]; then + echo "::warning::No matching linux/$RUNNER_ARCH workflow-plugin-compute-container binary in dist/artifacts.json; skipping verify-capabilities" + jq '.[] | {name, type, goos, goarch, path}' dist/artifacts.json + exit 0 + fi + wfctl plugin verify-capabilities --binary "$BIN" . + - name: Publish GitHub release + if: ${{ success() }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release edit "${{ github.ref_name }}" --draft=false --repo "${{ github.repository }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3f8921 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +dist/ +.release/ +*.bak diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..a5772cd --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,40 @@ +version: 2 + +before: + hooks: + - go mod tidy + - "sed -i.bak 's/\"version\": \".*\"/\"version\": \"{{ .Version }}\"/' plugin.json && rm -f plugin.json.bak" + +builds: + - id: workflow-plugin-compute-container + main: ./cmd/workflow-plugin-compute-container + binary: workflow-plugin-compute-container + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ldflags: + - -s -w -X main.version={{.Version}} -X github.com/GoCodeAlone/workflow-plugin-compute-container/internal.Version={{.Version}} + +archives: + - formats: [tar.gz] + name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}" + files: + - plugin.json + - plugin.contracts.json + - runtime-adapters.json + - LICENSE + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + +release: + draft: true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee1ede9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 GoCodeAlone + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index ca972b2..4788a39 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ registry allowlists, proof/reward mutation, and worker binding. invocation builders, env ref resolution, artifact hashing, and digest marker validation. +Runtime adapter metadata is published in `runtime-adapters.json` and referenced +from `plugin.json` through `runtimeAdaptersRef`. + ## Verification ```sh diff --git a/cmd/workflow-plugin-compute-container/main.go b/cmd/workflow-plugin-compute-container/main.go new file mode 100644 index 0000000..7d97d3c --- /dev/null +++ b/cmd/workflow-plugin-compute-container/main.go @@ -0,0 +1,15 @@ +package main + +import ( + plugin "github.com/GoCodeAlone/workflow-plugin-compute-container/internal" + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" +) + +var version = "0.0.0" + +func main() { + plugin.Version = version + sdk.Serve(plugin.NewPlugin(), + sdk.WithBuildVersion(sdk.ResolveBuildVersion(plugin.Version)), + ) +} diff --git a/container/adapters.go b/container/adapters.go new file mode 100644 index 0000000..9048f36 --- /dev/null +++ b/container/adapters.go @@ -0,0 +1,868 @@ +package container + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "slices" + "strings" + "time" + + core "github.com/GoCodeAlone/workflow-plugin-compute-core/protocol" +) + +const ( + SandboxedCommandProviderName = "sandboxed-command" + SandboxedCommandOperationRun = "run-sandboxed-command" + SandboxedContainerBuildProviderName = "sandboxed-container-build" + SandboxedContainerBuildOperation = "build-sandboxed-container" + + SandboxNetworkNone = "none" + SandboxNetworkBridge = "bridge" + + SandboxedContainerBuildStateDir = "/wfcompute-build" + SandboxedContainerBuildDigestPath = SandboxedContainerBuildStateDir + "/wfcompute-container-build-digest" + SandboxedContainerBuildDigestMarker = "WORKFLOW_COMPUTE_BUILD_DIGEST=" + + sandboxedContainerBuildDigestFile = ".wfcompute-container-build-digest" +) + +type ContainerRuntimeScope struct { + Args []string +} + +type SandboxMount struct { + HostPath string + ContainerPath string + ReadOnly bool + RequiredPrefix string +} + +type SandboxRunRequest struct { + Image string + Command []string + RuntimeScope ContainerRuntimeScope + Stdin []byte + Workspace string + WorkingDir string + Env map[string]string + Network string + RuntimeName string + RunAsRoot bool + WritableRootFS bool + AddCapabilities []string + ExtraTmpfs []string + DataMounts []SandboxMount + Timeout time.Duration + Limits core.ResourceLimits +} + +type SandboxRunResult struct { + ExitCode int + Stdout []byte + Stderr []byte + ArtifactHash string + ResourceUsage core.ResourceUsage +} + +type SandboxRuntime interface { + Available(context.Context) error + Run(context.Context, SandboxRunRequest) (SandboxRunResult, error) +} + +type SandboxedCommandRuntimeInvocation struct { + Request core.RuntimeExecutionRequest + Image string + Workspace string + Network string + Timeout time.Duration + TimeoutLimitHit string +} + +type SandboxedCommandInvocationOptions struct { + TaskID string + LeaseID string + Image string + Workspace string + Network string + Timeout time.Duration + TimeoutLimitHit string + Workload core.CommandWorkload + ResolvedEnv map[string]string + Limits core.ResourceLimits +} + +type SandboxedCommandRuntime interface { + RunSandboxedCommand(context.Context, SandboxedCommandRuntimeInvocation) (core.RuntimeExecutionResult, error) +} + +type SandboxRuntimeCommandAdapter struct { + Runtime SandboxRuntime +} + +type SandboxedContainerBuildRuntimeInvocation struct { + Request core.RuntimeExecutionRequest + Image string + Workspace string + Network string + Timeout time.Duration + TimeoutLimitHit string +} + +type SandboxedContainerBuildInvocationOptions struct { + TaskID string + LeaseID string + Image string + Workspace string + Network string + Timeout time.Duration + TimeoutLimitHit string + Workload core.ContainerBuildWorkload + ResolvedEnv map[string]string + Limits core.ResourceLimits +} + +type SandboxedContainerBuildRuntime interface { + BuildSandboxedContainer(context.Context, SandboxedContainerBuildRuntimeInvocation) (core.RuntimeExecutionResult, error) +} + +type SandboxRuntimeContainerBuildAdapter struct { + Runtime SandboxRuntime +} + +type RuntimeAdapterCatalogDocument struct { + Version string `json:"version"` + ProtocolVersion string `json:"protocol_version"` + Adapters []RuntimeAdapterCatalogEntry `json:"adapters"` + HostOwnedResponsibilities []string `json:"host_owned_responsibilities"` +} + +func (d RuntimeAdapterCatalogDocument) Validate() error { + var errs []error + if d.Version == "" { + errs = append(errs, errors.New("version is required")) + } + if d.ProtocolVersion != core.Version { + errs = append(errs, fmt.Errorf("protocol_version = %q, want %q", d.ProtocolVersion, core.Version)) + } + if len(d.Adapters) == 0 { + errs = append(errs, errors.New("at least one adapter is required")) + } + for i, adapter := range d.Adapters { + if err := adapter.Validate(); err != nil { + errs = append(errs, fmt.Errorf("adapters[%d]: %w", i, err)) + } + } + if len(d.HostOwnedResponsibilities) == 0 { + errs = append(errs, errors.New("host_owned_responsibilities is required")) + } + return errors.Join(errs...) +} + +type RuntimeAdapterCatalogEntry struct { + AdapterID string `json:"adapter_id"` + Operation string `json:"operation"` + Kinds []core.RuntimeAdapterKind `json:"kinds"` + WorkloadKinds []core.WorkloadKind `json:"workload_kinds"` + RuntimeProfiles []core.RuntimeProfile `json:"runtime_profiles"` + WorkspacePolicy core.RuntimeWorkspacePolicy `json:"workspace_policy"` + ConformanceProfiles []string `json:"conformance_profiles"` +} + +func (e RuntimeAdapterCatalogEntry) Validate() error { + var errs []error + if e.AdapterID == "" { + errs = append(errs, errors.New("adapter_id is required")) + } + if e.Operation == "" { + errs = append(errs, errors.New("operation is required")) + } + if len(e.Kinds) == 0 { + errs = append(errs, errors.New("kinds is required")) + } + if len(e.WorkloadKinds) == 0 { + errs = append(errs, errors.New("workload_kinds is required")) + } + if len(e.RuntimeProfiles) == 0 { + errs = append(errs, errors.New("runtime_profiles is required")) + } + if e.WorkspacePolicy == "" { + errs = append(errs, errors.New("workspace_policy is required")) + } + if len(e.ConformanceProfiles) == 0 { + errs = append(errs, errors.New("conformance_profiles is required")) + } + return errors.Join(errs...) +} + +func (e RuntimeAdapterCatalogEntry) Contract(descriptor core.RuntimeDescriptor) core.RuntimeAdapterContract { + if descriptor.Name == "" { + descriptor.Name = e.AdapterID + } + return core.RuntimeAdapterContract{ + ProtocolVersion: core.Version, + AdapterID: e.AdapterID, + Descriptor: descriptor, + Kinds: slices.Clone(e.Kinds), + WorkloadKinds: slices.Clone(e.WorkloadKinds), + RuntimeProfiles: slices.Clone(e.RuntimeProfiles), + WorkspacePolicy: e.WorkspacePolicy, + ConformanceProfiles: slices.Clone(e.ConformanceProfiles), + } +} + +func SandboxedCommandContract(descriptor core.RuntimeDescriptor) core.RuntimeAdapterContract { + if descriptor.Name == "" { + descriptor.Name = SandboxedCommandProviderName + } + return core.RuntimeAdapterContract{ + ProtocolVersion: core.Version, + AdapterID: SandboxedCommandProviderName, + Descriptor: descriptor, + Kinds: []core.RuntimeAdapterKind{core.RuntimeAdapterExecution}, + WorkloadKinds: []core.WorkloadKind{core.WorkloadCommand}, + RuntimeProfiles: []core.RuntimeProfile{core.RuntimeProfileSandboxedOCI}, + WorkspacePolicy: core.RuntimeWorkspaceRequired, + ConformanceProfiles: []string{"sandboxed-command-v1"}, + } +} + +func SandboxedContainerBuildContract(descriptor core.RuntimeDescriptor) core.RuntimeAdapterContract { + if descriptor.Name == "" { + descriptor.Name = SandboxedContainerBuildProviderName + } + return core.RuntimeAdapterContract{ + ProtocolVersion: core.Version, + AdapterID: SandboxedContainerBuildProviderName, + Descriptor: descriptor, + Kinds: []core.RuntimeAdapterKind{core.RuntimeAdapterExecution}, + WorkloadKinds: []core.WorkloadKind{core.WorkloadContainerBuild}, + RuntimeProfiles: []core.RuntimeProfile{core.RuntimeProfileContainerBuild}, + WorkspacePolicy: core.RuntimeWorkspaceRequired, + ConformanceProfiles: []string{"container-build-v1"}, + } +} + +func NewSandboxedCommandInvocation(opts SandboxedCommandInvocationOptions) (SandboxedCommandRuntimeInvocation, error) { + if strings.TrimSpace(opts.Workspace) == "" { + return SandboxedCommandRuntimeInvocation{}, errors.New("workspace is required") + } + if err := opts.Workload.Validate(); err != nil { + return SandboxedCommandRuntimeInvocation{}, err + } + if len(opts.Workload.Args) == 0 { + return SandboxedCommandRuntimeInvocation{}, errors.New("command args are required") + } + env, err := CommandRuntimeEnv(opts.Workload, opts.ResolvedEnv) + if err != nil { + return SandboxedCommandRuntimeInvocation{}, err + } + payload := opts.Workload + payload.Env = nil + input, err := json.Marshal(payload) + if err != nil { + return SandboxedCommandRuntimeInvocation{}, fmt.Errorf("marshal sandboxed-command runtime input: %w", err) + } + runtimeReq := core.RuntimeExecutionRequest{ + ProtocolVersion: core.Version, + TaskID: opts.TaskID, + LeaseID: opts.LeaseID, + WorkloadKind: core.WorkloadCommand, + Operation: SandboxedCommandOperationRun, + Input: input, + Env: env, + Limits: opts.Limits, + } + if err := runtimeReq.Validate(); err != nil { + return SandboxedCommandRuntimeInvocation{}, err + } + return SandboxedCommandRuntimeInvocation{ + Request: runtimeReq, + Image: opts.Image, + Workspace: opts.Workspace, + Network: firstNonEmpty(opts.Network, SandboxNetworkNone), + Timeout: defaultDuration(opts.Timeout, time.Minute), + TimeoutLimitHit: firstNonEmpty(opts.TimeoutLimitHit, "timeout"), + }, nil +} + +func (a SandboxRuntimeCommandAdapter) RunSandboxedCommand(ctx context.Context, invocation SandboxedCommandRuntimeInvocation) (core.RuntimeExecutionResult, error) { + if a.Runtime == nil { + return core.RuntimeExecutionResult{}, errors.New("sandbox runtime is required") + } + if err := invocation.Request.Validate(); err != nil { + return core.RuntimeExecutionResult{}, err + } + if invocation.Request.WorkloadKind != core.WorkloadCommand { + return core.RuntimeExecutionResult{}, fmt.Errorf("workload kind %q is not supported by sandboxed-command runtime", invocation.Request.WorkloadKind) + } + var workload core.CommandWorkload + if len(invocation.Request.Input) == 0 { + return core.RuntimeExecutionResult{}, errors.New("sandboxed-command runtime input is required") + } + if err := json.Unmarshal(invocation.Request.Input, &workload); err != nil { + return core.RuntimeExecutionResult{}, fmt.Errorf("decode sandboxed-command runtime input: %w", err) + } + if err := workload.Validate(); err != nil { + return core.RuntimeExecutionResult{}, err + } + if len(workload.Args) == 0 { + return core.RuntimeExecutionResult{}, errors.New("command args are required") + } + if err := a.Runtime.Available(ctx); err != nil { + return core.RuntimeExecutionResult{}, fmt.Errorf("sandbox runtime unavailable: %w", err) + } + timeout := defaultDuration(invocation.Timeout, time.Minute) + timeoutLimitHit := firstNonEmpty(invocation.TimeoutLimitHit, "timeout") + runCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + started := time.Now().UTC() + result, err := a.Runtime.Run(runCtx, SandboxRunRequest{ + Image: invocation.Image, + Command: slices.Clone(workload.Args), + Workspace: invocation.Workspace, + WorkingDir: workload.WorkingDirectory, + Env: cloneStringMap(invocation.Request.Env), + Network: firstNonEmpty(invocation.Network, SandboxNetworkNone), + Timeout: timeout, + Limits: invocation.Request.Limits, + }) + finished := time.Now().UTC() + runResult := core.RuntimeExecutionResult{ + StartedAt: started, + FinishedAt: finished, + ExitCode: result.ExitCode, + Stdout: result.Stdout, + Stderr: result.Stderr, + ArtifactHash: result.ArtifactHash, + ResourceUsage: result.ResourceUsage, + } + if runCtx.Err() != nil { + runResult.ResourceUsage.LimitHit = timeoutLimitHit + return runResult, fmt.Errorf("sandboxed command timed out after %s: %w", timeout, runCtx.Err()) + } + if err != nil { + return runResult, err + } + if len(workload.ArtifactAllowlist) > 0 { + artifactBase, hashErr := commandArtifactBaseDir(invocation.Workspace, workload.WorkingDirectory) + if hashErr != nil { + return runResult, hashErr + } + artifactHash, hashErr := hashArtifacts(artifactBase, workload.ArtifactAllowlist) + if hashErr != nil { + return runResult, hashErr + } + runResult.ArtifactHash = artifactHash + runResult.Artifacts = slices.Clone(workload.ArtifactAllowlist) + } + if runResult.ResourceUsage.WorkspaceBytes == 0 && invocation.Workspace != "" { + runResult.ResourceUsage.WorkspaceBytes = DirectorySize(invocation.Workspace) + } + return runResult, nil +} + +func NewSandboxedContainerBuildInvocation(opts SandboxedContainerBuildInvocationOptions) (SandboxedContainerBuildRuntimeInvocation, error) { + if err := opts.Workload.Validate(); err != nil { + return SandboxedContainerBuildRuntimeInvocation{}, err + } + if _, _, err := ResolveContainerBuildPaths(opts.Workspace, opts.Workload); err != nil { + return SandboxedContainerBuildRuntimeInvocation{}, err + } + env, err := ContainerBuildEnv(opts.Workload, opts.ResolvedEnv) + if err != nil { + return SandboxedContainerBuildRuntimeInvocation{}, err + } + payload := opts.Workload + payload.Env = nil + input, err := json.Marshal(payload) + if err != nil { + return SandboxedContainerBuildRuntimeInvocation{}, fmt.Errorf("marshal sandboxed-container-build runtime input: %w", err) + } + runtimeReq := core.RuntimeExecutionRequest{ + ProtocolVersion: core.Version, + TaskID: opts.TaskID, + LeaseID: opts.LeaseID, + WorkloadKind: core.WorkloadContainerBuild, + Operation: SandboxedContainerBuildOperation, + Input: input, + Env: env, + Limits: opts.Limits, + } + if err := runtimeReq.Validate(); err != nil { + return SandboxedContainerBuildRuntimeInvocation{}, err + } + return SandboxedContainerBuildRuntimeInvocation{ + Request: runtimeReq, + Image: opts.Image, + Workspace: opts.Workspace, + Network: firstNonEmpty(opts.Network, SandboxNetworkNone), + Timeout: defaultDuration(opts.Timeout, time.Hour), + TimeoutLimitHit: firstNonEmpty(opts.TimeoutLimitHit, "timeout"), + }, nil +} + +func (a SandboxRuntimeContainerBuildAdapter) BuildSandboxedContainer(ctx context.Context, invocation SandboxedContainerBuildRuntimeInvocation) (core.RuntimeExecutionResult, error) { + if a.Runtime == nil { + return core.RuntimeExecutionResult{}, errors.New("sandbox runtime is required") + } + if err := invocation.Request.Validate(); err != nil { + return core.RuntimeExecutionResult{}, err + } + if invocation.Request.WorkloadKind != core.WorkloadContainerBuild { + return core.RuntimeExecutionResult{}, fmt.Errorf("workload kind %q is not supported by sandboxed-container-build runtime", invocation.Request.WorkloadKind) + } + var workload core.ContainerBuildWorkload + if len(invocation.Request.Input) == 0 { + return core.RuntimeExecutionResult{}, errors.New("sandboxed-container-build runtime input is required") + } + if err := json.Unmarshal(invocation.Request.Input, &workload); err != nil { + return core.RuntimeExecutionResult{}, fmt.Errorf("decode sandboxed-container-build runtime input: %w", err) + } + if err := workload.Validate(); err != nil { + return core.RuntimeExecutionResult{}, err + } + _, dockerfile, err := ResolveContainerBuildPaths(invocation.Workspace, workload) + if err != nil { + return core.RuntimeExecutionResult{}, err + } + if err := a.Runtime.Available(ctx); err != nil { + return core.RuntimeExecutionResult{}, fmt.Errorf("sandbox runtime unavailable: %w", err) + } + timeout := defaultDuration(invocation.Timeout, time.Hour) + timeoutLimitHit := firstNonEmpty(invocation.TimeoutLimitHit, "timeout") + runCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + push := workload.PushTargetRef != "" + digestPath := filepath.Join(invocation.Workspace, sandboxedContainerBuildDigestFile) + started := time.Now().UTC() + runEnv := map[string]string{ + "WORKFLOW_COMPUTE_BUILD_CONTEXT": CleanContainerPath(workload.ContextDirectory), + "WORKFLOW_COMPUTE_BUILD_DOCKERFILE": dockerfile, + "WORKFLOW_COMPUTE_BUILD_TAG": firstTag(workload.Tags), + "WORKFLOW_COMPUTE_BUILD_TAGS": mustJSON(workload.Tags), + "WORKFLOW_COMPUTE_BUILD_PULL_TARGET_REF": workload.PullTargetRef, + "WORKFLOW_COMPUTE_BUILD_PUSH_TARGET_REF": workload.PushTargetRef, + "WORKFLOW_COMPUTE_BUILD_STATE_DIR": SandboxedContainerBuildStateDir, + "WORKFLOW_COMPUTE_BUILD_DIGEST_FILE": SandboxedContainerBuildDigestPath, + "WORKFLOW_COMPUTE_BUILD_PUSH": fmt.Sprintf("%t", push), + } + for key, value := range invocation.Request.Env { + if strings.HasPrefix(key, "WORKFLOW_COMPUTE_BUILD_") { + return core.RuntimeExecutionResult{}, fmt.Errorf("reserved workflow-compute build env %q", key) + } + runEnv[key] = value + } + result, err := a.Runtime.Run(runCtx, SandboxRunRequest{ + Image: invocation.Image, + Command: []string{"/usr/local/bin/wfcompute-container-builder"}, + Workspace: invocation.Workspace, + WorkingDir: ".", + RunAsRoot: true, + WritableRootFS: true, + AddCapabilities: []string{"CHOWN", "FOWNER"}, + ExtraTmpfs: []string{ + SandboxedContainerBuildStateDir + ":rw,noexec,nosuid,nodev,size=512m", + }, + Env: runEnv, + Network: firstNonEmpty(invocation.Network, SandboxNetworkNone), + Timeout: timeout, + Limits: invocation.Request.Limits, + }) + finished := time.Now().UTC() + runResult := core.RuntimeExecutionResult{ + StartedAt: started, + FinishedAt: finished, + ExitCode: result.ExitCode, + Stdout: result.Stdout, + Stderr: result.Stderr, + ArtifactHash: result.ArtifactHash, + ResourceUsage: result.ResourceUsage, + } + if runCtx.Err() != nil { + runResult.ResourceUsage.LimitHit = timeoutLimitHit + return runResult, fmt.Errorf("sandboxed container build timed out after %s: %w", timeout, runCtx.Err()) + } + if err != nil { + return runResult, err + } + if !ValidSHA256Digest(runResult.ArtifactHash) { + if digest := ParseSandboxedContainerBuildDigestMarker(runResult.Stdout); digest != "" { + runResult.ArtifactHash = digest + } + } + if !ValidSHA256Digest(runResult.ArtifactHash) { + if digest, readErr := ReadSandboxedContainerBuildDigest(digestPath); readErr == nil { + runResult.ArtifactHash = digest + } + } + if !ValidSHA256Digest(runResult.ArtifactHash) { + return runResult, errors.New("sandboxed container build did not produce a sha256 artifact digest") + } + if runResult.ResourceUsage.WorkspaceBytes == 0 && invocation.Workspace != "" { + runResult.ResourceUsage.WorkspaceBytes = DirectorySize(invocation.Workspace) + } + return runResult, nil +} + +func CommandRuntimeEnv(workload core.CommandWorkload, resolved map[string]string) (map[string]string, error) { + env := make(map[string]string, len(workload.Env)) + for _, ref := range workload.Env { + if _, exists := env[ref.Name]; exists { + return nil, fmt.Errorf("env %s is declared more than once", ref.Name) + } + value, ok := resolved[firstNonEmpty(ref.ValueRef, ref.SecretRef)] + if !ok { + return nil, fmt.Errorf("env %s missing resolved ref", ref.Name) + } + env[ref.Name] = value + } + return env, nil +} + +func ContainerBuildEnv(workload core.ContainerBuildWorkload, resolved map[string]string) (map[string]string, error) { + if len(workload.Env) == 0 { + return nil, nil + } + out := make(map[string]string, len(workload.Env)) + for _, ref := range workload.Env { + if strings.HasPrefix(ref.Name, "WORKFLOW_COMPUTE_BUILD_") { + return nil, fmt.Errorf("reserved workflow-compute build env %q", ref.Name) + } + if _, exists := out[ref.Name]; exists { + return nil, fmt.Errorf("env %s is declared more than once", ref.Name) + } + value, ok := resolved[firstNonEmpty(ref.ValueRef, ref.SecretRef)] + if !ok { + return nil, fmt.Errorf("env %s missing resolved ref", ref.Name) + } + out[ref.Name] = value + } + return out, nil +} + +func ResolveContainerBuildPaths(workspace string, workload core.ContainerBuildWorkload) (string, string, error) { + if strings.TrimSpace(workspace) == "" { + return "", "", errors.New("workspace is required") + } + root := filepath.Clean(workspace) + contextDir, err := resolveInside(root, workload.ContextDirectory) + if err != nil { + return "", "", err + } + if err := rejectSymlinkPathComponents(root, contextDir); err != nil { + return "", "", err + } + dockerfile := workload.Dockerfile + if dockerfile == "" { + dockerfile = "Dockerfile" + } + dockerfilePath, err := resolveInside(contextDir, dockerfile) + if err != nil { + return "", "", err + } + if err := rejectSymlinkPathComponents(contextDir, dockerfilePath); err != nil { + return "", "", err + } + return contextDir, dockerfile, nil +} + +func ValidSHA256Digest(value string) bool { + const prefix = "sha256:" + if !strings.HasPrefix(value, prefix) { + return false + } + hexValue := strings.TrimPrefix(value, prefix) + if len(hexValue) != 64 { + return false + } + for _, r := range hexValue { + if (r < '0' || r > '9') && (r < 'a' || r > 'f') { + return false + } + } + return true +} + +func ParseSandboxedContainerBuildDigestMarker(output []byte) string { + var digest string + for _, line := range strings.Split(string(output), "\n") { + value, ok := strings.CutPrefix(strings.TrimSpace(line), SandboxedContainerBuildDigestMarker) + if ok { + if ValidSHA256Digest(value) { + digest = value + } else { + digest = "" + } + } + } + return digest +} + +func CleanContainerPath(value string) string { + if value == "" || value == "." { + return "." + } + return strings.TrimPrefix(filepath.ToSlash(filepath.Clean(value)), "/") +} + +func ReadSandboxedContainerBuildDigest(path string) (string, error) { + info, err := os.Lstat(path) + if err != nil { + return "", err + } + if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() { + return "", fmt.Errorf("sandboxed container-build digest path must be regular file") + } + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + openInfo, err := file.Stat() + if err != nil { + return "", err + } + if !openInfo.Mode().IsRegular() || !os.SameFile(info, openInfo) { + return "", fmt.Errorf("sandboxed container-build digest path changed while opening") + } + data, err := io.ReadAll(file) + if err != nil { + return "", err + } + digest := strings.TrimSpace(string(data)) + if !ValidSHA256Digest(digest) { + return "", fmt.Errorf("invalid sandboxed container-build digest %q", digest) + } + return digest, nil +} + +func DirectorySize(root string) int64 { + var total int64 + _ = filepath.WalkDir(root, func(_ string, entry os.DirEntry, err error) error { + if err != nil || entry.IsDir() { + return nil + } + info, err := entry.Info() + if err != nil { + return nil + } + total += info.Size() + return nil + }) + return total +} + +func commandArtifactBaseDir(workspace, workingDirectory string) (string, error) { + if err := rejectSymlinkPathComponents(workspace, workspace); err != nil { + return "", err + } + if workingDirectory == "" { + return workspace, nil + } + path, err := resolveInside(workspace, workingDirectory) + if err != nil { + return "", err + } + if err := rejectSymlinkPathComponents(workspace, path); err != nil { + return "", err + } + return path, nil +} + +func hashArtifacts(baseDir string, allowlist []string) (string, error) { + if baseDir == "" { + return "", errors.New("workspace is required for artifact allowlist") + } + hash := sha256.New() + paths := slices.Sorted(slices.Values(allowlist)) + for _, allowed := range paths { + path, err := resolveInside(baseDir, allowed) + if err != nil { + return "", err + } + if err := rejectSymlinkPathComponents(baseDir, path); err != nil { + return "", err + } + info, err := os.Lstat(path) + if err != nil { + return "", fmt.Errorf("stat artifact %s: %w", allowed, err) + } + if info.Mode()&os.ModeSymlink != 0 { + return "", fmt.Errorf("artifact %s is a symlink", allowed) + } + if info.IsDir() { + if err := filepath.WalkDir(path, func(child string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if entry.IsDir() { + return nil + } + if entry.Type()&os.ModeSymlink != 0 { + rel, _ := filepath.Rel(baseDir, child) + return fmt.Errorf("artifact %s is a symlink", filepath.ToSlash(rel)) + } + return hashFile(hash, baseDir, child) + }); err != nil { + return "", err + } + continue + } + if err := hashFile(hash, baseDir, path); err != nil { + return "", err + } + } + return "sha256:" + hex.EncodeToString(hash.Sum(nil)), nil +} + +func hashFile(hash io.Writer, baseDir, path string) error { + rel, err := filepath.Rel(baseDir, path) + if err != nil { + return err + } + pathInfo, err := os.Lstat(path) + if err != nil { + return err + } + if pathInfo.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("artifact %s is a symlink", filepath.ToSlash(rel)) + } + if !pathInfo.Mode().IsRegular() { + return fmt.Errorf("artifact %s is not a regular file", filepath.ToSlash(rel)) + } + if _, err := fmt.Fprintf(hash, "%s\x00", filepath.ToSlash(rel)); err != nil { + return err + } + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + openInfo, err := file.Stat() + if err != nil { + return err + } + if !os.SameFile(pathInfo, openInfo) { + return fmt.Errorf("artifact %s changed while opening", filepath.ToSlash(rel)) + } + if _, err := io.Copy(hash, file); err != nil { + return err + } + _, err = hash.Write([]byte{0}) + return err +} + +func resolveInside(baseDir, value string) (string, error) { + if value == "" { + return baseDir, nil + } + if filepath.IsAbs(value) { + return "", fmt.Errorf("path %q must be relative", value) + } + path := filepath.Clean(filepath.Join(baseDir, value)) + if baseDir == "" { + return path, nil + } + rel, err := filepath.Rel(baseDir, path) + if err != nil { + return "", err + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("path %q escapes workspace", value) + } + return path, nil +} + +func rejectSymlinkPathComponents(root, target string) error { + root = filepath.Clean(root) + target = filepath.Clean(target) + if root == "" || root == "." { + return nil + } + rootInfo, err := os.Lstat(root) + if err != nil { + return err + } + if rootInfo.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("path %q is a symlink", root) + } + if target == root { + return nil + } + rel, err := filepath.Rel(root, target) + if err != nil { + return err + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) { + return fmt.Errorf("path %q escapes workspace", target) + } + current := root + for _, part := range strings.Split(rel, string(filepath.Separator)) { + if part == "." || part == "" { + continue + } + current = filepath.Join(current, part) + info, err := os.Lstat(current) + if errors.Is(err, os.ErrNotExist) { + return nil + } + if err != nil { + return err + } + if info.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("path %q is a symlink", current) + } + } + return nil +} + +func firstNonEmpty(values ...string) string { + for _, value := range values { + if strings.TrimSpace(value) != "" { + return value + } + } + return "" +} + +func defaultDuration(value, fallback time.Duration) time.Duration { + if value > 0 { + return value + } + return fallback +} + +func firstTag(tags []string) string { + if len(tags) == 0 { + return "" + } + return tags[0] +} + +func mustJSON(value any) string { + data, err := json.Marshal(value) + if err != nil { + return "null" + } + return string(data) +} + +func cloneStringMap(in map[string]string) map[string]string { + if len(in) == 0 { + return nil + } + out := make(map[string]string, len(in)) + for key, value := range in { + out[key] = value + } + return out +} diff --git a/container/adapters_test.go b/container/adapters_test.go new file mode 100644 index 0000000..17ba548 --- /dev/null +++ b/container/adapters_test.go @@ -0,0 +1,410 @@ +package container + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "slices" + "strings" + "testing" + "time" + + core "github.com/GoCodeAlone/workflow-plugin-compute-core/protocol" +) + +func TestSandboxedCommandInvocationResolvesEnvRefsAndStripsPayloadRefs(t *testing.T) { + invocation, err := NewSandboxedCommandInvocation(SandboxedCommandInvocationOptions{ + TaskID: "task-1", + LeaseID: "lease-1", + Image: "ghcr.io/gocodealone/command@sha256:abc", + Workspace: t.TempDir(), + Network: SandboxNetworkNone, + Timeout: 45 * time.Second, + Workload: core.CommandWorkload{ + Args: []string{"sh", "-c", "printf ok > out.txt"}, + WorkingDirectory: "work", + Env: []core.EnvRef{ + {Name: "TOKEN", SecretRef: "secret://token"}, + }, + ArtifactAllowlist: []string{"out.txt"}, + }, + ResolvedEnv: map[string]string{"secret://token": "redacted-token"}, + }) + if err != nil { + t.Fatalf("build invocation: %v", err) + } + if invocation.Request.ProtocolVersion != core.Version { + t.Fatalf("protocol version = %q, want %q", invocation.Request.ProtocolVersion, core.Version) + } + if invocation.Request.Operation != SandboxedCommandOperationRun { + t.Fatalf("operation = %q, want %q", invocation.Request.Operation, SandboxedCommandOperationRun) + } + if got := invocation.Request.Env["TOKEN"]; got != "redacted-token" { + t.Fatalf("resolved env TOKEN = %q", got) + } + var payload core.CommandWorkload + if err := json.Unmarshal(invocation.Request.Input, &payload); err != nil { + t.Fatalf("decode input payload: %v", err) + } + if len(payload.Env) != 0 { + t.Fatalf("runtime payload leaked env refs: %+v", payload.Env) + } +} + +func TestSandboxedCommandInvocationRequiresWorkspace(t *testing.T) { + _, err := NewSandboxedCommandInvocation(SandboxedCommandInvocationOptions{ + TaskID: "task-1", + LeaseID: "lease-1", + Image: "sandbox-image", + Workload: core.CommandWorkload{ + Args: []string{"true"}, + }, + }) + if err == nil || !strings.Contains(err.Error(), "workspace is required") { + t.Fatalf("expected workspace rejection, got %v", err) + } +} + +func TestSandboxRuntimeCommandAdapterRunsHardenedSandboxAndHashesArtifacts(t *testing.T) { + workspace := t.TempDir() + if err := os.Mkdir(filepath.Join(workspace, "work"), 0o700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(workspace, "work", "out.txt"), []byte("artifact"), 0o600); err != nil { + t.Fatal(err) + } + runtime := &recordingSandboxRuntime{ + result: SandboxRunResult{ + Stdout: []byte("ok"), + }, + } + invocation, err := NewSandboxedCommandInvocation(SandboxedCommandInvocationOptions{ + TaskID: "task-1", + LeaseID: "lease-1", + Image: "sandbox-image", + Workspace: workspace, + Network: SandboxNetworkNone, + Timeout: time.Minute, + Workload: core.CommandWorkload{ + Args: []string{"sh", "-c", "true"}, + WorkingDirectory: "work", + Env: []core.EnvRef{{Name: "TOKEN", SecretRef: "secret://token"}}, + ArtifactAllowlist: []string{"out.txt"}, + }, + ResolvedEnv: map[string]string{"secret://token": "resolved"}, + }) + if err != nil { + t.Fatalf("build invocation: %v", err) + } + result, err := (SandboxRuntimeCommandAdapter{Runtime: runtime}).RunSandboxedCommand(context.Background(), invocation) + if err != nil { + t.Fatalf("run sandboxed command: %v", err) + } + if got, want := runtime.request.Image, "sandbox-image"; got != want { + t.Fatalf("image = %q, want %q", got, want) + } + if got := strings.Join(runtime.request.Command, " "); got != "sh -c true" { + t.Fatalf("command = %q", got) + } + if runtime.request.Network != SandboxNetworkNone { + t.Fatalf("network = %q", runtime.request.Network) + } + if runtime.request.Env["TOKEN"] != "resolved" { + t.Fatalf("sandbox env TOKEN = %q", runtime.request.Env["TOKEN"]) + } + if result.ArtifactHash == "" || !strings.HasPrefix(result.ArtifactHash, "sha256:") { + t.Fatalf("artifact hash = %q", result.ArtifactHash) + } + if !slices.Equal(result.Artifacts, []string{"out.txt"}) { + t.Fatalf("artifacts = %+v", result.Artifacts) + } +} + +func TestSandboxRuntimeContainerBuildAdapterUsesStrictBuildSandboxAndDigestMarker(t *testing.T) { + digest := "sha256:" + strings.Repeat("a", 64) + runtime := &recordingSandboxRuntime{ + result: SandboxRunResult{ + Stdout: []byte("log\n" + SandboxedContainerBuildDigestMarker + digest + "\n"), + }, + } + workspace := t.TempDir() + if err := os.WriteFile(filepath.Join(workspace, "Dockerfile"), []byte("FROM scratch\n"), 0o600); err != nil { + t.Fatal(err) + } + invocation, err := NewSandboxedContainerBuildInvocation(SandboxedContainerBuildInvocationOptions{ + TaskID: "task-2", + LeaseID: "lease-2", + Image: "builder-image", + Workspace: workspace, + Network: SandboxNetworkNone, + Timeout: time.Hour, + Workload: core.ContainerBuildWorkload{ + ContextDirectory: ".", + Dockerfile: "Dockerfile", + Tags: []string{"example:test"}, + Env: []core.EnvRef{ + {Name: "DOCKER_CONFIG_JSON", SecretRef: "secret://docker-config"}, + }, + }, + ResolvedEnv: map[string]string{"secret://docker-config": "{}"}, + }) + if err != nil { + t.Fatalf("build invocation: %v", err) + } + result, err := (SandboxRuntimeContainerBuildAdapter{Runtime: runtime}).BuildSandboxedContainer(context.Background(), invocation) + if err != nil { + t.Fatalf("run sandboxed container build: %v", err) + } + if result.ArtifactHash != digest { + t.Fatalf("artifact hash = %q, want %q", result.ArtifactHash, digest) + } + if !runtime.request.RunAsRoot { + t.Fatal("container-build sandbox must run as root for builder compatibility") + } + if !runtime.request.WritableRootFS { + t.Fatal("container-build sandbox must use writable rootfs for builder compatibility") + } + if !slices.Contains(runtime.request.AddCapabilities, "CHOWN") || !slices.Contains(runtime.request.AddCapabilities, "FOWNER") { + t.Fatalf("missing strict builder capabilities: %+v", runtime.request.AddCapabilities) + } + if runtime.request.Network != SandboxNetworkNone { + t.Fatalf("network = %q", runtime.request.Network) + } + if runtime.request.Env["DOCKER_CONFIG_JSON"] != "{}" { + t.Fatalf("docker config env = %q", runtime.request.Env["DOCKER_CONFIG_JSON"]) + } + if runtime.request.Env["WORKFLOW_COMPUTE_BUILD_DOCKERFILE"] != "Dockerfile" { + t.Fatalf("dockerfile env = %q", runtime.request.Env["WORKFLOW_COMPUTE_BUILD_DOCKERFILE"]) + } + if runtime.request.Env["WORKFLOW_COMPUTE_BUILD_TAGS"] != `["example:test"]` { + t.Fatalf("tags env = %q", runtime.request.Env["WORKFLOW_COMPUTE_BUILD_TAGS"]) + } +} + +func TestContainerBuildRejectsReservedControlEnvOverride(t *testing.T) { + _, err := NewSandboxedContainerBuildInvocation(SandboxedContainerBuildInvocationOptions{ + TaskID: "task-2", + LeaseID: "lease-2", + Image: "builder-image", + Workspace: t.TempDir(), + Workload: core.ContainerBuildWorkload{ + ContextDirectory: ".", + Tags: []string{"example:test"}, + Env: []core.EnvRef{ + {Name: "WORKFLOW_COMPUTE_BUILD_DIGEST_FILE", SecretRef: "secret://override"}, + }, + }, + ResolvedEnv: map[string]string{"secret://override": "/outside"}, + }) + if err == nil || !strings.Contains(err.Error(), "reserved workflow-compute build env") { + t.Fatalf("expected reserved env rejection, got %v", err) + } +} + +func TestContainerBuildInvocationRequiresWorkspace(t *testing.T) { + _, err := NewSandboxedContainerBuildInvocation(SandboxedContainerBuildInvocationOptions{ + TaskID: "task-2", + LeaseID: "lease-2", + Image: "builder-image", + Workload: core.ContainerBuildWorkload{ + ContextDirectory: ".", + Tags: []string{"example:test"}, + }, + }) + if err == nil || !strings.Contains(err.Error(), "workspace is required") { + t.Fatalf("expected workspace rejection, got %v", err) + } +} + +func TestContainerBuildPassesAllTagsAsJSON(t *testing.T) { + runtime := &recordingSandboxRuntime{ + result: SandboxRunResult{ArtifactHash: "sha256:" + strings.Repeat("a", 64)}, + } + workspace := t.TempDir() + if err := os.WriteFile(filepath.Join(workspace, "Dockerfile"), []byte("FROM scratch\n"), 0o600); err != nil { + t.Fatal(err) + } + invocation, err := NewSandboxedContainerBuildInvocation(SandboxedContainerBuildInvocationOptions{ + TaskID: "task-2", + LeaseID: "lease-2", + Image: "builder-image", + Workspace: workspace, + Workload: core.ContainerBuildWorkload{ + ContextDirectory: ".", + Tags: []string{"example:one", "example:two"}, + }, + }) + if err != nil { + t.Fatal(err) + } + if _, err := (SandboxRuntimeContainerBuildAdapter{Runtime: runtime}).BuildSandboxedContainer(context.Background(), invocation); err != nil { + t.Fatal(err) + } + if got := runtime.request.Env["WORKFLOW_COMPUTE_BUILD_TAGS"]; got != `["example:one","example:two"]` { + t.Fatalf("WORKFLOW_COMPUTE_BUILD_TAGS = %q", got) + } +} + +func TestContainerBuildPassesPushAndPullTargetRefs(t *testing.T) { + runtime := &recordingSandboxRuntime{ + result: SandboxRunResult{ArtifactHash: "sha256:" + strings.Repeat("a", 64)}, + } + workspace := t.TempDir() + if err := os.WriteFile(filepath.Join(workspace, "Dockerfile"), []byte("FROM scratch\n"), 0o600); err != nil { + t.Fatal(err) + } + invocation, err := NewSandboxedContainerBuildInvocation(SandboxedContainerBuildInvocationOptions{ + TaskID: "task-2", + LeaseID: "lease-2", + Image: "builder-image", + Workspace: workspace, + Workload: core.ContainerBuildWorkload{ + ContextDirectory: ".", + Tags: []string{"example:one"}, + PushTargetRef: "push://registry/app", + PullTargetRef: "pull://registry/base", + }, + }) + if err != nil { + t.Fatal(err) + } + if _, err := (SandboxRuntimeContainerBuildAdapter{Runtime: runtime}).BuildSandboxedContainer(context.Background(), invocation); err != nil { + t.Fatal(err) + } + if got := runtime.request.Env["WORKFLOW_COMPUTE_BUILD_PUSH_TARGET_REF"]; got != "push://registry/app" { + t.Fatalf("push target env = %q", got) + } + if got := runtime.request.Env["WORKFLOW_COMPUTE_BUILD_PULL_TARGET_REF"]; got != "pull://registry/base" { + t.Fatalf("pull target env = %q", got) + } +} + +func TestContainerBuildRejectsSymlinkedContextAndDockerfilePaths(t *testing.T) { + t.Run("context", func(t *testing.T) { + workspace := t.TempDir() + outside := t.TempDir() + if err := os.Symlink(outside, filepath.Join(workspace, "context-link")); err != nil { + t.Skipf("symlink unavailable: %v", err) + } + _, _, err := ResolveContainerBuildPaths(workspace, core.ContainerBuildWorkload{ + ContextDirectory: "context-link", + Dockerfile: "Dockerfile", + Tags: []string{"example:test"}, + }) + if err == nil || !strings.Contains(err.Error(), "is a symlink") { + t.Fatalf("expected symlinked context rejection, got %v", err) + } + }) + t.Run("dockerfile", func(t *testing.T) { + workspace := t.TempDir() + outside := t.TempDir() + if err := os.WriteFile(filepath.Join(outside, "Dockerfile"), []byte("FROM scratch\n"), 0o600); err != nil { + t.Fatal(err) + } + if err := os.Symlink(filepath.Join(outside, "Dockerfile"), filepath.Join(workspace, "Dockerfile")); err != nil { + t.Skipf("symlink unavailable: %v", err) + } + _, _, err := ResolveContainerBuildPaths(workspace, core.ContainerBuildWorkload{ + ContextDirectory: ".", + Dockerfile: "Dockerfile", + Tags: []string{"example:test"}, + }) + if err == nil || !strings.Contains(err.Error(), "is a symlink") { + t.Fatalf("expected symlinked dockerfile rejection, got %v", err) + } + }) +} + +func TestCommandArtifactHashRejectsSymlinkPathComponents(t *testing.T) { + workspace := t.TempDir() + outside := t.TempDir() + if err := os.WriteFile(filepath.Join(outside, "out.txt"), []byte("secret"), 0o600); err != nil { + t.Fatal(err) + } + if err := os.Symlink(outside, filepath.Join(workspace, "link")); err != nil { + t.Skipf("symlink unavailable: %v", err) + } + runtime := &recordingSandboxRuntime{result: SandboxRunResult{Stdout: []byte("ok")}} + invocation, err := NewSandboxedCommandInvocation(SandboxedCommandInvocationOptions{ + TaskID: "task-1", + LeaseID: "lease-1", + Image: "sandbox-image", + Workspace: workspace, + Workload: core.CommandWorkload{ + Args: []string{"true"}, + ArtifactAllowlist: []string{"link/out.txt"}, + }, + }) + if err != nil { + t.Fatal(err) + } + _, err = (SandboxRuntimeCommandAdapter{Runtime: runtime}).RunSandboxedCommand(context.Background(), invocation) + if err == nil || !strings.Contains(err.Error(), "is a symlink") { + t.Fatalf("expected symlink component rejection, got %v", err) + } +} + +func TestRuntimeAdapterContractsDescribePublicCommandAndContainerBuildAdapters(t *testing.T) { + command := SandboxedCommandContract(core.RuntimeDescriptor{ + Name: SandboxedCommandProviderName, + Version: "v1.0.0", + ExecutionSecurityTier: core.ExecutionSandboxedContainer, + ProofTier: core.ProofArtifactHash, + ImageDigest: "sha256:" + strings.Repeat("b", 64), + RootFSDigest: "sha256:" + strings.Repeat("c", 64), + }) + if command.AdapterID != SandboxedCommandProviderName { + t.Fatalf("command adapter id = %q", command.AdapterID) + } + if !slices.Equal(command.WorkloadKinds, []core.WorkloadKind{core.WorkloadCommand}) { + t.Fatalf("command workload kinds = %+v", command.WorkloadKinds) + } + if !slices.Equal(command.RuntimeProfiles, []core.RuntimeProfile{core.RuntimeProfileSandboxedOCI}) { + t.Fatalf("command runtime profiles = %+v", command.RuntimeProfiles) + } + if command.WorkspacePolicy != core.RuntimeWorkspaceRequired { + t.Fatalf("command workspace policy = %q", command.WorkspacePolicy) + } + if err := command.Validate(); err != nil { + t.Fatalf("command contract invalid: %v", err) + } + + build := SandboxedContainerBuildContract(core.RuntimeDescriptor{ + Name: SandboxedContainerBuildProviderName, + Version: "v1.0.0", + ExecutionSecurityTier: core.ExecutionSandboxedContainer, + ProofTier: core.ProofArtifactHash, + ImageDigest: "sha256:" + strings.Repeat("d", 64), + RootFSDigest: "sha256:" + strings.Repeat("e", 64), + }) + if build.AdapterID != SandboxedContainerBuildProviderName { + t.Fatalf("container-build adapter id = %q", build.AdapterID) + } + if !slices.Equal(build.WorkloadKinds, []core.WorkloadKind{core.WorkloadContainerBuild}) { + t.Fatalf("container-build workload kinds = %+v", build.WorkloadKinds) + } + if !slices.Equal(build.RuntimeProfiles, []core.RuntimeProfile{core.RuntimeProfileContainerBuild}) { + t.Fatalf("container-build runtime profiles = %+v", build.RuntimeProfiles) + } + if !slices.Equal(build.ConformanceProfiles, []string{"container-build-v1"}) { + t.Fatalf("container-build conformance profiles = %+v", build.ConformanceProfiles) + } + if err := build.Validate(); err != nil { + t.Fatalf("container-build contract invalid: %v", err) + } +} + +type recordingSandboxRuntime struct { + request SandboxRunRequest + result SandboxRunResult +} + +func (r *recordingSandboxRuntime) Available(context.Context) error { + return nil +} + +func (r *recordingSandboxRuntime) Run(_ context.Context, req SandboxRunRequest) (SandboxRunResult, error) { + r.request = req + return r.result, nil +} diff --git a/examples/minimal/config.yaml b/examples/minimal/config.yaml new file mode 100644 index 0000000..7fde565 --- /dev/null +++ b/examples/minimal/config.yaml @@ -0,0 +1,3 @@ +plugins: + - name: workflow-plugin-compute-container + version: 0.0.0 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..616c00b --- /dev/null +++ b/go.mod @@ -0,0 +1,151 @@ +module github.com/GoCodeAlone/workflow-plugin-compute-container + +go 1.26.0 + +require ( + github.com/GoCodeAlone/workflow v0.62.0 + github.com/GoCodeAlone/workflow-plugin-compute-core v0.3.1 +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/DataDog/datadog-go/v5 v5.8.3 // indirect + github.com/GoCodeAlone/go-plugin v1.7.0 // indirect + github.com/GoCodeAlone/modular v1.13.0 // indirect + github.com/GoCodeAlone/modular/modules/auth v1.15.0 // indirect + github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 // indirect + github.com/GoCodeAlone/yaegi v0.17.2 // indirect + github.com/IBM/sarama v1.47.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.6 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.16 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.15 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 // indirect + github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 // indirect + github.com/aws/smithy-go v1.25.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudevents/sdk-go/v2 v2.16.2 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/danieljoos/wincred v1.2.3 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/go-connections v0.7.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/eapache/go-resiliency v1.7.0 // indirect + github.com/eapache/queue v1.1.0 // indirect + github.com/expr-lang/expr v1.17.8 // indirect + github.com/fatih/color v1.19.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golobby/cast v1.3.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/hashicorp/vault/api v1.23.0 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/itchyny/gojq v0.12.18 // indirect + github.com/itchyny/timefmt-go v0.1.7 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.9.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect + github.com/klauspost/compress v1.18.6 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nats-io/nats.go v1.52.0 // indirect + github.com/nats-io/nkeys v0.4.15 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/oklog/run v1.2.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect + github.com/redis/go-redis/v9 v9.19.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.2.0 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/zalando/go-keyring v0.2.8 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.28.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/net v0.54.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.70.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.47.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..806c244 --- /dev/null +++ b/go.sum @@ -0,0 +1,496 @@ +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSqGF5n0s= +github.com/DataDog/datadog-go/v5 v5.8.3/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/GoCodeAlone/go-plugin v1.7.0 h1:EwnhqPlXiNmp85S+MXnKKvm3YlfA6O4NzBb4+GSlEVY= +github.com/GoCodeAlone/go-plugin v1.7.0/go.mod h1:HbGQRZUIa+jbDfjsaZIMJYvrz+LnxL0mJpggfynSTMk= +github.com/GoCodeAlone/modular v1.13.0 h1:UfsegfAmPWcPYQOqYZFsw/LNySBmMDcthiOQe5bscqE= +github.com/GoCodeAlone/modular v1.13.0/go.mod h1:b06Pvgcc8HsGxvl30iO39zGH2jIWz467QEj2+OQL2Do= +github.com/GoCodeAlone/modular/modules/auth v1.15.0 h1:pBSkPSf4k4GLSbUQFLuPa+nFbfoJXGzSz9q89VoapZk= +github.com/GoCodeAlone/modular/modules/auth v1.15.0/go.mod h1:vmIm/LQrcURS2p02YwaELb+CZoHPtT0XB0v1i+sj9i4= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 h1:buYs0TGNbAZgtTq1Qb+dfmTv3+ZOBIN0HbvVBLyNqxE= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0/go.mod h1:329flAKmwrPq2JEwu9iltWv6A83H/Di82Xze+kvdKDw= +github.com/GoCodeAlone/workflow v0.62.0 h1:emFkTomDpVmBcEfw7quRO4V/J4qDsWNx/CrBdlGqkfg= +github.com/GoCodeAlone/workflow v0.62.0/go.mod h1:659GGDrw3QJ7b625y9rf8QhKIpt1VCoEG0MxKu5tGQs= +github.com/GoCodeAlone/workflow-plugin-compute-core v0.3.1 h1:vsUaEl5TkUWfGDzTuJnfR8+aimdWU2cUvX363yKKXLw= +github.com/GoCodeAlone/workflow-plugin-compute-core v0.3.1/go.mod h1:1T6uCpUWPCNk6XPYgKq5CL/7LkD24MphKYsYVzF4jnI= +github.com/GoCodeAlone/yaegi v0.17.2 h1:WK6Y6e0t1a6U7r+S2dN3CGWW1PizYD3zO0zneToZPxM= +github.com/GoCodeAlone/yaegi v0.17.2/go.mod h1:z5Pr6Wse6QJcQvpgxTxzMAevFarH0N37TG88Y9dprx0= +github.com/IBM/sarama v1.47.0 h1:GcQFEd12+KzfPYeLgN69Fh7vLCtYRhVIx0rO4TZO318= +github.com/IBM/sarama v1.47.0/go.mod h1:7gLLIU97nznOmA6TX++Qds+DRxH89P2XICY2KAQUzAY= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/alicebob/miniredis/v2 v2.36.1 h1:Dvc5oAnNOr7BIfPn7tF269U8DvRW1dBG2D5n0WrfYMI= +github.com/alicebob/miniredis/v2 v2.36.1/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op h1:Ucf+QxEKMbPogRO5guBNe5cgd9uZgfoJLOYs8WWhtjM= +github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg= +github.com/aws/aws-sdk-go-v2 v1.41.6/go.mod h1:dy0UzBIfwSeot4grGvY1AqFWN5zgziMmWGzysDnHFcQ= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/config v1.32.16 h1:Q0iQ7quUgJP0F/SCRTieScnaMdXr9h/2+wze1u3cNeM= +github.com/aws/aws-sdk-go-v2/config v1.32.16/go.mod h1:duCCnJEFqpt2RC6no1iK6q+8HpwOAkiUua0pY507dQc= +github.com/aws/aws-sdk-go-v2/credentials v1.19.15 h1:fyvgWTszojq8hEnMi8PPBTvZdTtEVmAVyo+NFLHBhH4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.15/go.mod h1:gJiYyMOjNg8OEdRWOf3CrFQxM2a98qmrtjx1zuiQfB8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 h1:IOGsJ1xVWhsi+ZO7/NW8OuZZBtMJLZbk4P5HDjJO0jQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22/go.mod h1:b+hYdbU+jGKfXE8kKM6g1+h+L/Go3vMvzlxBsiuGsxg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 h1:GmLa5Kw1ESqtFpXsx5MmC84QWa/ZrLZvlJGa2y+4kcQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22/go.mod h1:6sW9iWm9DK9YRpRGga/qzrzNLgKpT2cIxb7Vo2eNOp0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 h1:dY4kWZiSaXIzxnKlj17nHnBcXXBfac6UlsAx2qL6XrU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22/go.mod h1:KIpEUx0JuRZLO7U6cbV204cWAEco2iC3l061IxlwLtI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 h1:FPXsW9+gMuIeKmz7j6ENWcWtBGTe1kH8r9thNt5Uxx4= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23/go.mod h1:7J8iGMdRKk6lw2C+cMIphgAnT8uTwBwNOsGkyOCm80U= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 h1:HtOTYcbVcGABLOVuPYaIihj6IlkqubBwFj10K5fxRek= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8/go.mod h1:VsK9abqQeGlzPgUr+isNWzPlK2vKe9INMLWnY65f5Xs= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 h1:PUmZeJU6Y1Lbvt9WFuJ0ugUK2xn6hIWUBBbKuOWF30s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22/go.mod h1:nO6egFBoAaoXze24a2C0NjQCvdpk8OueRoYimvEB9jo= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4 h1:3m9iJtMtLq75jKRAfw0kapoHUlbzi0CRVigysBN/FHA= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4/go.mod h1:O2L6vGm4xacEuN2otHFMgn7yXXlgzFKzxrba0fy/yk8= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 h1:a1Fq/KXn75wSzoJaPQTgZO0wHGqE9mjFnylnqEPTchA= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.10/go.mod h1:p6+MXNxW7IA6dMgHfTAzljuwSKD0NCm/4lbS4t6+7vI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 h1:x6bKbmDhsgSZwv6q19wY/u3rLk/3FGjJWyqKcIRufpE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.16/go.mod h1:CudnEVKRtLn0+3uMV0yEXZ+YZOKnAtUJ5DmDhilVnIw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 h1:oK/njaL8GtyEihkWMD4k3VgHCT64RQKkZwh0DG5j8ak= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20/go.mod h1:JHs8/y1f3zY7U5WcuzoJ/yAYGYtNIVPKLIbp61euvmg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 h1:ks8KBcZPh3PYISr5dAiXCM5/Thcuxk8l+PG4+A0exds= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.0/go.mod h1:pFw33T0WLvXU3rw1WBkpMlkgIn54eCB5FYLhjDc9Foo= +github.com/aws/smithy-go v1.25.0 h1:Sz/XJ64rwuiKtB6j98nDIPyYrV1nVNJ4YU74gttcl5U= +github.com/aws/smithy-go v1.25.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM= +github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudevents/sdk-go/v2 v2.16.2 h1:ZYDFrYke4FD+jM8TZTJJO6JhKHzOQl2oqpFK1D+NnQM= +github.com/cloudevents/sdk-go/v2 v2.16.2/go.mod h1:laOcGImm4nVJEU+PHnUrKL56CKmRL65RlQF0kRmW/kg= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI= +github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0= +github.com/cucumber/godog v0.15.1 h1:rb/6oHDdvVZKS66hrhpjFQFHjthFSrQBCOI1LwshNTI= +github.com/cucumber/godog v0.15.1/go.mod h1:qju+SQDewOljHuq9NSM66s0xEhogx0q30flfxL4WUk8= +github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= +github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= +github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= +github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golobby/cast v1.3.3 h1:s2Lawb9RMz7YyYf8IrfMQY4IFmA1R/lgfmj97Vc6fig= +github.com/golobby/cast v1.3.3/go.mod h1:0oDO5IT84HTXcbLDf1YXuk0xtg/cRDrxhbpWKxwtJCY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= +github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo= +github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.23.0 h1:gXgluBsSECfRWTSW9niY2jwg2e9mMJc4WoHNv4g3h6A= +github.com/hashicorp/vault/api v1.23.0/go.mod h1:zransKiB9ftp+kgY8ydjnvCU7Wk8i9L0DYWpXeMj9ko= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= +github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg= +github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA= +github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= +github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk= +github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= +github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= +github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= +github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts= +github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg= +github.com/nats-io/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc= +github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= +github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= +github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= +github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= +github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs= +github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 h1:3WsB1FAbiRIf2tOxscWKs3pQBD9he1NsrnbhMuWfekc= +google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60/go.mod h1:7yoXV7RIh5gblj/xVYoogxAWvA9wUeVbpsK/M694l00= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk= +modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/plugin.go b/internal/plugin.go new file mode 100644 index 0000000..474c68a --- /dev/null +++ b/internal/plugin.go @@ -0,0 +1,28 @@ +// Package internal implements the workflow-plugin-compute-container plugin. +package internal + +import ( + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" +) + +// Version is set at build time via -ldflags +// "-X github.com/GoCodeAlone/workflow-plugin-compute-container/internal.Version=X.Y.Z". +var Version = "0.0.0" + +// ComputeContainerPlugin exposes command/container-build runtime adapter metadata. +type ComputeContainerPlugin struct{} + +// NewPlugin returns a new plugin instance. main.go calls sdk.Serve(NewPlugin()). +func NewPlugin() sdk.PluginProvider { + return &ComputeContainerPlugin{} +} + +// Manifest returns plugin metadata used by the workflow engine for discovery. +func (p *ComputeContainerPlugin) Manifest() sdk.PluginManifest { + return sdk.PluginManifest{ + Name: "workflow-plugin-compute-container", + Version: Version, + Author: "GoCodeAlone", + Description: "Public command and container-build runtime adapter plugin for Workflow Compute.", + } +} diff --git a/internal/plugin_test.go b/internal/plugin_test.go new file mode 100644 index 0000000..69a07a2 --- /dev/null +++ b/internal/plugin_test.go @@ -0,0 +1,70 @@ +package internal + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + + container "github.com/GoCodeAlone/workflow-plugin-compute-container/container" + core "github.com/GoCodeAlone/workflow-plugin-compute-core/protocol" +) + +func TestManifest(t *testing.T) { + plugin := NewPlugin() + manifest := plugin.Manifest() + if manifest.Name != "workflow-plugin-compute-container" { + t.Fatalf("name = %q", manifest.Name) + } + if manifest.Version != Version { + t.Fatalf("version = %q, want %q", manifest.Version, Version) + } + if manifest.Description == "" { + t.Fatal("description is required") + } +} + +func TestPluginJSONReferencesRuntimeAdapters(t *testing.T) { + root := filepath.Clean("..") + data, err := os.ReadFile(filepath.Join(root, "plugin.json")) + if err != nil { + t.Fatal(err) + } + var manifest struct { + RuntimeAdaptersRef string `json:"runtimeAdaptersRef"` + } + if err := json.Unmarshal(data, &manifest); err != nil { + t.Fatal(err) + } + if manifest.RuntimeAdaptersRef != "runtime-adapters.json" { + t.Fatalf("runtimeAdaptersRef = %q", manifest.RuntimeAdaptersRef) + } + adapters, err := os.ReadFile(filepath.Join(root, manifest.RuntimeAdaptersRef)) + if err != nil { + t.Fatal(err) + } + var catalog container.RuntimeAdapterCatalogDocument + if err := json.Unmarshal(adapters, &catalog); err != nil { + t.Fatal(err) + } + if err := catalog.Validate(); err != nil { + t.Fatalf("runtime adapter catalog invalid: %v", err) + } + if len(catalog.Adapters) != 2 { + t.Fatalf("runtime adapter catalog incomplete: %+v", catalog) + } + for _, adapter := range catalog.Adapters { + contract := adapter.Contract(core.RuntimeDescriptor{ + Name: adapter.AdapterID, + Version: "v1.0.0", + ExecutionSecurityTier: core.ExecutionSandboxedContainer, + ProofTier: core.ProofArtifactHash, + ImageDigest: "sha256:" + strings.Repeat("a", 64), + RootFSDigest: "sha256:" + strings.Repeat("b", 64), + }) + if err := contract.Validate(); err != nil { + t.Fatalf("runtime adapter contract for %s invalid: %v", adapter.AdapterID, err) + } + } +} diff --git a/plugin.contracts.json b/plugin.contracts.json new file mode 100644 index 0000000..eaeeab4 --- /dev/null +++ b/plugin.contracts.json @@ -0,0 +1,4 @@ +{ + "version": "1", + "contracts": [] +} diff --git a/plugin.json b/plugin.json new file mode 100644 index 0000000..95982c4 --- /dev/null +++ b/plugin.json @@ -0,0 +1,32 @@ +{ + "name": "workflow-plugin-compute-container", + "version": "0.0.0", + "description": "Public command and container-build runtime adapter plugin for Workflow Compute.", + "author": "GoCodeAlone", + "license": "MIT", + "type": "external", + "tier": "community", + "private": false, + "minEngineVersion": "0.64.7", + "keywords": [ + "compute", + "container-build", + "runtime-adapter" + ], + "homepage": "https://github.com/GoCodeAlone/workflow-plugin-compute-container", + "repository": "https://github.com/GoCodeAlone/workflow-plugin-compute-container", + "runtimeAdaptersRef": "runtime-adapters.json", + "dependencies": [ + { + "name": "workflow-plugin-compute-core", + "constraint": ">=0.3.1" + } + ], + "capabilities": { + "configProvider": false, + "moduleTypes": [], + "stepTypes": [], + "triggerTypes": [] + }, + "contracts": [] +} diff --git a/runtime-adapters.json b/runtime-adapters.json new file mode 100644 index 0000000..41b322b --- /dev/null +++ b/runtime-adapters.json @@ -0,0 +1,33 @@ +{ + "version": "1", + "protocol_version": "compute.v1alpha1", + "adapters": [ + { + "adapter_id": "sandboxed-command", + "operation": "run-sandboxed-command", + "kinds": ["execution"], + "workload_kinds": ["command"], + "runtime_profiles": ["sandboxed-oci-v1"], + "workspace_policy": "required", + "conformance_profiles": ["sandboxed-command-v1"] + }, + { + "adapter_id": "sandboxed-container-build", + "operation": "build-sandboxed-container", + "kinds": ["execution"], + "workload_kinds": ["container-build"], + "runtime_profiles": ["container-build-v1"], + "workspace_policy": "required", + "conformance_profiles": ["container-build-v1"] + } + ], + "host_owned_responsibilities": [ + "task-admission", + "leases", + "authorization", + "credential-resolution", + "registry-allowlists", + "proof-reward-mutation", + "worker-binding" + ] +}