From 6eff66e6684b724039923aeea7d835be1bd3b76b Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sat, 3 Oct 2026 06:20:09 -0400 Subject: [PATCH] feat(github): add audited runner policy CLI Keep policy mutations provider-neutral and journal their exact authority. Serialize full transactions across CLI processes and retain restrictive rollback after response loss. --- .goreleaser.yaml | 23 + cmd/workflow-plugin-github/main.go | 2 +- go.mod | 102 ++- go.sum | 409 +++++------ internal/cli.go | 143 ++++ internal/cli_launch_test.go | 263 ++++++++ internal/cli_test.go | 29 + internal/plugin.go | 2 +- internal/policy_lock.go | 40 ++ internal/policy_lock_test.go | 81 +++ internal/runner_policy.go | 820 +++++++++++++++++++++++ internal/runner_policy_test.go | 473 +++++++++++++ plugin.contracts.json | 1 + plugin.json | 13 +- provider_input_schema.go | 22 + release_packaging_test.go | 169 +++++ schemas/github-runner-policy.schema.json | 27 + 17 files changed, 2323 insertions(+), 296 deletions(-) create mode 100644 internal/cli.go create mode 100644 internal/cli_launch_test.go create mode 100644 internal/cli_test.go create mode 100644 internal/policy_lock.go create mode 100644 internal/policy_lock_test.go create mode 100644 internal/runner_policy.go create mode 100644 internal/runner_policy_test.go create mode 100644 schemas/github-runner-policy.schema.json diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2a9bcd9..14229c8 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -2,6 +2,9 @@ version: 2 project_name: workflow-plugin-github +snapshot: + version_template: "{{ .Version }}" + before: hooks: - go mod tidy -diff @@ -11,9 +14,19 @@ before: - 'cp plugin.json "{{ .Env.GORELEASER_RENDER_DIR }}/plugin.json"' - 'cp contracts/github-runner-provider.json "{{ .Env.GORELEASER_RENDER_DIR }}/contracts/github-runner-provider.json"' - "sed -i.bak 's/\"version\": \".*\"/\"version\": \"{{ .Version }}\"/' \"{{ .Env.GORELEASER_RENDER_DIR }}/plugin.json\" && rm -f \"{{ .Env.GORELEASER_RENDER_DIR }}/plugin.json.bak\"" + - "sed -i.bak 's#/download/v[^/]*/#/download/v{{ .Version }}/#g' \"{{ .Env.GORELEASER_RENDER_DIR }}/plugin.json\" && rm -f \"{{ .Env.GORELEASER_RENDER_DIR }}/plugin.json.bak\"" - "sed -i.bak 's/\"version\": \"v.*\"/\"version\": \"v{{ .Version }}\"/' \"{{ .Env.GORELEASER_RENDER_DIR }}/contracts/github-runner-provider.json\" && rm -f \"{{ .Env.GORELEASER_RENDER_DIR }}/contracts/github-runner-provider.json.bak\"" builds: + - id: github-cli + main: ./cmd/workflow-plugin-github + binary: github + env: + - CGO_ENABLED=0 + goos: [linux, darwin, windows] + goarch: [amd64, arm64] + ldflags: + - -s -w -X github.com/GoCodeAlone/workflow-plugin-github/internal.Version={{.Version}} - id: workflow-plugin-github main: ./cmd/workflow-plugin-github binary: workflow-plugin-github @@ -57,6 +70,16 @@ builds: - arm64 archives: + - id: github-cli + ids: [github-cli] + name_template: "github-{{ .Os }}-{{ .Arch }}" + files: + - src: "{{ .Env.GORELEASER_RENDER_DIR }}/plugin.json" + dst: plugin.json + - plugin.contracts.json + - schemas/github-runner-policy.schema.json + - README.md + - LICENSE - id: workflow-plugin-github allow_different_binary_count: true ids: diff --git a/cmd/workflow-plugin-github/main.go b/cmd/workflow-plugin-github/main.go index a71fb66..3030caa 100644 --- a/cmd/workflow-plugin-github/main.go +++ b/cmd/workflow-plugin-github/main.go @@ -10,7 +10,7 @@ import ( ) func main() { - sdk.Serve(internal.NewGitHubPlugin(), + sdk.ServePluginFull(internal.NewGitHubPlugin(), internal.NewCLI(), nil, sdk.WithBuildVersion(sdk.ResolveBuildVersion(internal.Version)), ) } diff --git a/go.mod b/go.mod index 54a8c36..26f79c4 100644 --- a/go.mod +++ b/go.mod @@ -3,34 +3,36 @@ module github.com/GoCodeAlone/workflow-plugin-github go 1.26.5 require ( - github.com/GoCodeAlone/workflow v0.64.0 + github.com/GoCodeAlone/workflow v0.86.0 github.com/GoCodeAlone/workflow-plugin-compute-core v0.8.3 github.com/coreos/go-systemd/v22 v22.7.0 + github.com/gofrs/flock v0.12.1 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/go-github/v69 v69.2.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.57.0 golang.org/x/crypto/x509roots/fallback v0.0.0-20260712151947-c1a3b97d708a - golang.org/x/sys v0.45.0 + golang.org/x/sys v0.48.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af + gopkg.in/yaml.v3 v3.0.1 ) require ( 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/cache v1.15.0 // indirect - github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 // indirect - github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0 // indirect - github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0 // indirect + github.com/GoCodeAlone/modular v1.13.6 // indirect + github.com/GoCodeAlone/modular/modules/auth v1.19.0 // indirect + github.com/GoCodeAlone/modular/modules/cache v1.19.0 // indirect + github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.10.0 // indirect + github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0 // indirect + github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0 // indirect github.com/GoCodeAlone/yaegi v0.17.2 // indirect - github.com/IBM/sarama v1.47.0 // indirect + github.com/IBM/sarama v1.50.3 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/RoaringBitmap/roaring v1.9.4 // indirect github.com/Workiva/go-datastructures v1.1.7 // indirect - github.com/andybalholm/brotli v1.2.1 // indirect + github.com/andybalholm/brotli v1.2.2 // indirect github.com/armon/go-metrics v0.4.1 // 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 @@ -49,36 +51,30 @@ require ( 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/bits-and-blooms/bitset v1.24.4 // indirect + github.com/bits-and-blooms/bitset v1.24.5 // indirect github.com/bytedance/gopkg v0.1.4 // indirect - github.com/bytedance/sonic v1.15.1 // indirect + github.com/bytedance/sonic v1.15.2 // indirect github.com/bytedance/sonic/loader v0.5.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/cloudwego/base64x v0.1.7 // 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/deckarep/golang-set/v2 v2.9.0 // 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/felixge/httpsnoop v1.1.0 // indirect github.com/flowchartsman/retry v1.2.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fxamacker/cbor/v2 v2.9.2 // 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/go-openapi/jsonpointer v0.24.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/golobby/cast v1.3.3 // indirect @@ -91,7 +87,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-metrics v0.6.0 // indirect github.com/hashicorp/go-msgpack/v2 v2.1.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect @@ -106,11 +102,11 @@ require ( github.com/hashicorp/memberlist v0.5.4 // 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/itchyny/gojq v0.12.19 // indirect + github.com/itchyny/timefmt-go v0.1.8 // 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/pgx/v5 v5.10.0 // 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 @@ -118,34 +114,31 @@ require ( 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/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect + github.com/klauspost/compress v1.19.0 // indirect + github.com/klauspost/cpuid/v2 v2.4.0 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.22 // indirect github.com/miekg/dns v1.1.72 // 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/mschoch/smat v0.2.0 // 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/nkeys v0.4.16 // 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/pierrec/lz4/v4 v4.1.27 // 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/redis/go-redis/v9 v9.21.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/reugn/go-quartz v0.15.2 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect @@ -154,8 +147,8 @@ require ( github.com/tidwall/btree v1.8.1 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/redcon v1.6.2 // indirect - github.com/tochemey/goakt/v4 v4.2.4 // indirect - github.com/tochemey/olric v0.3.9 // indirect + github.com/tochemey/goakt/v4 v4.3.1 // indirect + github.com/tochemey/olric v0.3.11 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect @@ -165,36 +158,35 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/zalando/go-keyring v0.2.8 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect - go.etcd.io/bbolt v1.4.3 // indirect + go.etcd.io/bbolt v1.5.0 // indirect go.mongodb.org/mongo-driver v1.17.9 // 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/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.44.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/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.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/arch v0.27.0 // indirect - golang.org/x/mod v0.36.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/arch v0.28.0 // indirect + golang.org/x/mod v0.41.0 // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sync v0.23.0 // indirect + golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.45.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 + golang.org/x/tools v0.49.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 // indirect + google.golang.org/grpc v1.83.2 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.70.0 // indirect + modernc.org/libc v1.73.4 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.47.0 // indirect + modernc.org/sqlite v1.53.0 // indirect ) diff --git a/go.sum b/go.sum index 2c2ed01..3576c33 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= @@ -10,26 +9,26 @@ github.com/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSq 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/cache v1.15.0 h1:6Y2EJ5S7mb/TjyG/uN6dto5VUYJNDFYULUamRsqAKvo= -github.com/GoCodeAlone/modular/modules/cache v1.15.0/go.mod h1:PRun74dRZKfqlBM+f6QrvI9oa4joUU3j1hisiLyQ+oM= -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/modular/modules/jsonschema v1.15.0 h1:xb1mI4NZkzvNKQ2F6nkyXQvK/kEvvfs1z7FoGf3/LRA= -github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0/go.mod h1:hhGouwAVsonmJ4Lain4jINZ9nZCoc9l9eF3BHbmR8eE= -github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0 h1:cvdLHbM/vzvygQTcAWSJsy+dAPzzwWyjzKMmTBFcFIo= -github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0/go.mod h1:/9ipMG4qM2CHQ14BfXKdVlYRJelef6M8MFI5TbZv67M= -github.com/GoCodeAlone/workflow v0.64.0 h1:2CpbYPwIqdGDb3xi3YJpwcteIum4ehBSrnRql/1YvB4= -github.com/GoCodeAlone/workflow v0.64.0/go.mod h1:659GGDrw3QJ7b625y9rf8QhKIpt1VCoEG0MxKu5tGQs= +github.com/GoCodeAlone/modular v1.13.6 h1:AIdfdR/KTPHX8T5KJBXUHMokliutUHjl6jRfkqdh/7Q= +github.com/GoCodeAlone/modular v1.13.6/go.mod h1:Vs92YltRNqA/v3peoHdqHTCjD10ww7rY3iURf1Ju450= +github.com/GoCodeAlone/modular/modules/auth v1.19.0 h1:ughD47BqEAhg7+3trucwcs5KzEpq9gIS/6jY4nkeGtA= +github.com/GoCodeAlone/modular/modules/auth v1.19.0/go.mod h1:pGPazN7Irlza8kUxE7WsXri44PhURKYBGym1tlBUl+k= +github.com/GoCodeAlone/modular/modules/cache v1.19.0 h1:s+3aFI7wncV0NhbPrJheFyVYr1WXwLJXOvSAP9RRaYU= +github.com/GoCodeAlone/modular/modules/cache v1.19.0/go.mod h1:bj/yS2FPdEDb8Vdmd5hjs5W4hchzMAC57eoIB3YY9S4= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.10.0 h1:2ljVafd/1LYchF47WrnA1+ji8mcmVXMJ4F5qDrhZZi4= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.10.0/go.mod h1:AKLcRGsw5gp2Q1zhuK0TBnMJOsaRe3aJ2OKnLFE2O5w= +github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0 h1:zoWioqUvuNNDfnjHA1sHixdlHfBreJdGhnnEBtxkzI8= +github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0/go.mod h1:GDU/jsD6AddmXKedj0wZwieUIaQsTBSGMzuj+XHXMrw= +github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0 h1:+2M/ecyCxDiXfJM4ibcERuu/BBeIbLTQNcVgRsllR64= +github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0/go.mod h1:tlVH1mA5yuU8CB7R7+HXIRaBixZoNid6h+5tew5u3FU= +github.com/GoCodeAlone/workflow v0.86.0 h1:9NatmWg2120RHDFesltawOAS7jJvzyJ4k9Q+5WueySg= +github.com/GoCodeAlone/workflow v0.86.0/go.mod h1:WaH+HhX8lrmoGu/vQjkyloZTydUWZVVRo9KFJsZAUtI= github.com/GoCodeAlone/workflow-plugin-compute-core v0.8.3 h1:CIu6hIUXJx6PbmTq1xP9RKNuXJ9kBsIiJQoRuBvyj2U= github.com/GoCodeAlone/workflow-plugin-compute-core v0.8.3/go.mod h1:Dsw3miQN8eenpDaLzo/p0TolGtI7NyETkf9abAHZKSM= 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/IBM/sarama v1.50.3 h1:zpY2iZYmt+z+0Bo3aYF+cD48OBt2hIgiDPZUuZKTXcc= +github.com/IBM/sarama v1.50.3/go.mod h1:Jo4MSfdDT3ycmQj7/ab8eLZwnvwCKZm/8H7SCbtyo8U= 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= @@ -41,13 +40,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -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/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= -github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/antithesishq/antithesis-sdk-go v0.7.0 h1:uWDG8BqLD1lI2ps38WDz2vXflrTX2+vLX0SvZtztJtE= -github.com/antithesishq/antithesis-sdk-go v0.7.0/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= +github.com/alicebob/miniredis/v2 v2.38.0 h1:nZAzCR+Lj+Vxk4ZXzm2NuKq2O33RXj1XxJ2e2uP9jiw= +github.com/alicebob/miniredis/v2 v2.38.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM= +github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/antithesishq/antithesis-sdk-go v0.7.2 h1:oEEedg1Xgi8drRjqB0f9tfjhLoInE0IYZfZ6zAhQUbY= +github.com/antithesishq/antithesis-sdk-go v0.7.2/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg= @@ -87,8 +85,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce 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/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= -github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.24.5 h1:654xBVHc23gJMAgOTkPNoCVfiRxuIOAUnAZFtopqJ4w= +github.com/bits-and-blooms/bitset v1.24.5/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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= @@ -97,8 +95,8 @@ github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBO github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY= github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= -github.com/bytedance/sonic v1.15.1 h1:nJD5PmM0vY7J8CT6MxoqbVAAMhkSmV2HgRAUrrpLoOw= -github.com/bytedance/sonic v1.15.1/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo= +github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -147,8 +145,6 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -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= @@ -157,10 +153,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp 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/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= -github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= +github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= @@ -168,26 +162,22 @@ github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40 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/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/flowchartsman/retry v1.2.0 h1:qDhlw6RNufXz6RGr+IiYimFpMMkt77SUSHY5tgFaUCU= github.com/flowchartsman/retry v1.2.0/go.mod h1:+sfx8OgCCiAr3t5jh2Gk+T0fRTI+k52edaYxURQxY64= -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/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= +github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= 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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 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= @@ -195,34 +185,34 @@ 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-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= -github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= -github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= -github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= -github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= -github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= -github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= -github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= -github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= -github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/jsonpointer v0.24.0 h1:AA6mCjHYHmZ+1RU2Js089EaOK/iwXXNwQsTgnsTha2M= +github.com/go-openapi/jsonpointer v0.24.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= +github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= +github.com/go-openapi/swag v0.27.0 h1:8ecSuZlh4NXc3GsmAOqECIYqDTApCWaMe3gO4gjJNEE= +github.com/go-openapi/swag v0.27.0/go.mod h1:Kkgz9Ht0+ul9/aVdFmc9xSyPzUwf/aFF5KiFPBXfSY0= +github.com/go-openapi/swag/cmdutils v0.27.0 h1:aIKiqhB29AaP+7xm8/CPg3uOpeHx2SUp6TvMpu/a31Y= +github.com/go-openapi/swag/cmdutils v0.27.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.0 h1:EKOH4feXrvdo8DbSsXSAqRT8fz1epEnS5O2IfXUOzE8= +github.com/go-openapi/swag/conv v0.27.0/go.mod h1:pfiv0uKQTbaGApk8Zs/lZV3uSjmSpa2FO1y183YngN8= +github.com/go-openapi/swag/fileutils v0.27.0 h1:ib5jMUqGq5tY1EyO4inlrabsaeDAleFU+XD1FXQcgp8= +github.com/go-openapi/swag/fileutils v0.27.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.0 h1:VYtd9jEQYeU4j8q5vdn5KWotF4vKywhGdMBrALtAsfE= +github.com/go-openapi/swag/jsonutils v0.27.0/go.mod h1:U7pb8AGuwhok3RDicHeHwSG4L3PXSq6PAL98Aon632g= +github.com/go-openapi/swag/loading v0.27.0 h1:s8DA9aPEdFH6OluHUYUn3DnIuoTdyWs9RwffXBUfyeI= +github.com/go-openapi/swag/loading v0.27.0/go.mod h1:VOz+Jg6UGGywcmRvYsI4fvtp+bd7NfioseGEPleYdA4= +github.com/go-openapi/swag/mangling v0.27.0 h1:rpPJuqQHa6z2pDiP3iIpXOyNXlSs9cQCxnJSAxzdfOc= +github.com/go-openapi/swag/mangling v0.27.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.0 h1:lEUG+hHvPvLggB3A8snFk0IRKNf9uC0YKc+7WYqvAF8= +github.com/go-openapi/swag/netutils v0.27.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/stringutils v0.27.0 h1:Of7w/HljWsNZvuxsUAnw3n+hCOyI6HLJOxW2kQRAxio= +github.com/go-openapi/swag/stringutils v0.27.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.0 h1:aCf4MSGo8NLwZP8Q6t32DWLJSvl/WwNqgmEG+xJ6v2o= +github.com/go-openapi/swag/typeutils v0.27.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.0 h1:bQ6eAMil5X9tdcf7dMn4t15alzG6jddnrKPuKa/zxKM= +github.com/go-openapi/swag/yamlutils v0.27.0/go.mod h1:yRfIo7qqVkmJRQjX8exjA3AfcI8rH1KDNPsTparoCv4= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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= @@ -232,6 +222,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 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/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= 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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -243,13 +235,6 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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= @@ -258,12 +243,9 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE= @@ -283,8 +265,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 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/consul/api v1.34.2 h1:B5jqSSKwWyY8U8WiGS5vmPEPkkF0bAvrECykdZkDR80= -github.com/hashicorp/consul/api v1.34.2/go.mod h1:+gAdHQa2zvgYX3ZfcgITtnYCSj6AgS/cgotvCKaE+b8= +github.com/hashicorp/consul/api/v2 v2.0.0 h1:FBzxiwnP8kJoHL2ByDUSvaItbc7jF7LxZLTnHtO75wA= +github.com/hashicorp/consul/api/v2 v2.0.0/go.mod h1:1gEhzpcnl4X4ui7JFpFQvn7QRYQ60IFm6BxBXjrv0dg= 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= @@ -298,8 +280,8 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ 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-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= -github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-metrics v0.6.0 h1:+kjWqHRH2HxAocneVfB/BI6EeWUUHyPhyQZozMT8Ed4= +github.com/hashicorp/go-metrics v0.6.0/go.mod h1:0B52B5pZ7+qm5Zhzs8Fygr87isvmUgr0Zv9rmJ9qsnQ= github.com/hashicorp/go-msgpack/v2 v2.1.5 h1:Ue879bPnutj/hXfmUk6s/jtIK90XxgiUIcXRl656T44= github.com/hashicorp/go-msgpack/v2 v2.1.5/go.mod h1:bjCsRXpZ7NsJdk45PoCQnzRGDaK8TKm5ZnDI/9y3J4M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -336,16 +318,16 @@ github.com/hashicorp/vault/api v1.23.0 h1:gXgluBsSECfRWTSW9niY2jwg2e9mMJc4WoHNv4 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/itchyny/gojq v0.12.19 h1:ttXA0XCLEMoaLOz5lSeFOZ6u6Q3QxmG46vfgI4O0DEs= +github.com/itchyny/gojq v0.12.19/go.mod h1:5galtVPDywX8SPSOrqjGxkBeDhSxEW1gSxoy7tn1iZY= +github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo791OI= +github.com/itchyny/timefmt-go v0.1.8/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/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/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= @@ -362,23 +344,18 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ 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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kapetan-io/tackle v0.15.0 h1:J+D04RuxEKtybzCjuzgcmQuFBiEpYa+5vPU2mAgAxTs= github.com/kapetan-io/tackle v0.15.0/go.mod h1:pDr4mjpo2RQO/q/je1dGuGwnBVwZcsRp60wgDV2hA3c= -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/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= +github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -396,8 +373,8 @@ github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8S github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 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-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/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= @@ -417,16 +394,14 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= -github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= -github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= -github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= +github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= +github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -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/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8= +github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= @@ -441,22 +416,19 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb 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/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU= -github.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg= -github.com/nats-io/nats-server/v2 v2.14.0 h1:+8q0HrDFotwLLcGH/legOEOnowunhK+aZ4GYBIWpQlM= -github.com/nats-io/nats-server/v2 v2.14.0/go.mod h1:ImVUUDvfClJbb6cuJQRc1VmgDCXKM5ds0OoiG9MVOKo= +github.com/nats-io/jwt/v2 v2.8.2 h1:XXRgB60MSTnqsRwejQurVDs/hcv2dkt+86GjI+I/bMc= +github.com/nats-io/jwt/v2 v2.8.2/go.mod h1:Ag/56sq9OblL4JgdYufDd16Egb17Kr/8WwwuO/forVc= +github.com/nats-io/nats-server/v2 v2.14.3 h1:+xjydPt7rkit67G+04TN0mcO2n+8nveZE7tK/PPV53A= +github.com/nats-io/nats-server/v2 v2.14.3/go.mod h1:5IlCtBzfwyzQzPMjmoJ9W2/LKmnJRtNyuOs/OT+NHDY= 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/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= 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= @@ -470,8 +442,8 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -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/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= +github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -485,8 +457,6 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:Om github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 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.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -496,21 +466,17 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 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.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 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/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E= +github.com/redis/go-redis/v9 v9.21.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/reugn/go-quartz v0.15.2 h1:IQUnwTtNURVtdcwH4CJhFH3dXAUwP2fXZaNjPp+sJAY= @@ -526,11 +492,10 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEV github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY= -github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM= +github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= @@ -555,12 +520,12 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= -github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= -github.com/testcontainers/testcontainers-go/modules/consul v0.42.0 h1:oQqQAPaiv5WvLB6lCapjohWRbMi1pYmPSTSDQrVv3nc= -github.com/testcontainers/testcontainers-go/modules/consul v0.42.0/go.mod h1:5/t9MNZTBLJ08QzPdVe0XXjLg7W31+udMM3+hoRYXa4= -github.com/testcontainers/testcontainers-go/modules/etcd v0.42.0 h1:Hy4Zt7/JfoNW35Vz99lH/yeRMgRy7ebxnwNJPHhpkZg= -github.com/testcontainers/testcontainers-go/modules/etcd v0.42.0/go.mod h1:+2oLnkMw0McOfhjlXEljY7LoXruENqsTaSIeHFy/VWU= +github.com/testcontainers/testcontainers-go v0.43.0 h1:oEQx5MW2DGd9z3AeEQfB2lPM0eLs7ztyaGRu75bFo5A= +github.com/testcontainers/testcontainers-go v0.43.0/go.mod h1:+VxkT2NQnKOZPKi6praMuMKYHYyOGXr0XSBSlSMCzFo= +github.com/testcontainers/testcontainers-go/modules/consul v0.43.0 h1:IjDMn8vwZPYkW7ahGDBZ+RSvQcFeV39S+Uw0yJ9LDAs= +github.com/testcontainers/testcontainers-go/modules/consul v0.43.0/go.mod h1:epRLzBhmLH1bQed1P+asQlrofs3D//q0KJgwq2vV+Mg= +github.com/testcontainers/testcontainers-go/modules/etcd v0.43.0 h1:nTrmoY5WZkio3/EEhOE6bHRkR+W7PBmoaaB/C8k66iA= +github.com/testcontainers/testcontainers-go/modules/etcd v0.43.0/go.mod h1:avfg/77osD4sT3nhQdecBfkC3BK9SgxvTMr/uuqruG0= github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= @@ -574,10 +539,10 @@ github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRU github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= -github.com/tochemey/goakt/v4 v4.2.4 h1:yGW8GSWEnaPhPWy4jdVGj1ki91CRCBv6sgKRr3nEAhA= -github.com/tochemey/goakt/v4 v4.2.4/go.mod h1:ekFwa36wG383Rf0fzn+LKA0TWELAas0EqyoKbAIZNFU= -github.com/tochemey/olric v0.3.9 h1:MU3VVQ3TZwdRzyxai0myxNMZj0lMK/RCjhaYh2Xe6aQ= -github.com/tochemey/olric v0.3.9/go.mod h1:r5OVAIw1zaZJ5WKvKj1c4XnLwFaYpH8EJpm4dAD8Bp0= +github.com/tochemey/goakt/v4 v4.3.1 h1:l6JBN/kr1qN9tFki2dfDUDIwhmBnirxEp7h8CGZNyq8= +github.com/tochemey/goakt/v4 v4.3.1/go.mod h1:QcHNrLicr/4+dqzI0OEJ//8rv0TxpvbqHsEkWSuCcAc= +github.com/tochemey/olric v0.3.11 h1:GMvXyIc6eeBgFknHVLyVS1GMBQoi8HhNU/ml1cZ1Nm4= +github.com/tochemey/olric v0.3.11/go.mod h1:lauJDgUkt283EIMzO1mMh9wLLVQSwREtBpF/lYX3JKU= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -611,34 +576,34 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 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.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= -go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.11 h1:XFGTgrJ8nak3kB4NgMG8t7NT+lEeuuvKQAqUHKVgkWQ= -go.etcd.io/etcd/api/v3 v3.6.11/go.mod h1:HYfTh0jyh+uFgp6gMbxJteIDYY97yMuYz85Rnw6Gy9o= -go.etcd.io/etcd/client/pkg/v3 v3.6.11 h1:e41mp315Yn3QMGPmEzCyLsMINgJXTY/dX8kM++1csxU= -go.etcd.io/etcd/client/pkg/v3 v3.6.11/go.mod h1:DysuMe/inqRyC/1tjRR6hReH/VV9Lufs27YKSKBWWJg= -go.etcd.io/etcd/client/v3 v3.6.11 h1:LAByD96VmmeuairkvdAcE0RZnrmGz/q3ceeWePo9bwc= -go.etcd.io/etcd/client/v3 v3.6.11/go.mod h1:vOTDMCo+fGPEClJqcFEFSqZ+8e7WKV7AyqJjX//HR2w= +go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU= +go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk= +go.etcd.io/etcd/api/v3 v3.6.13 h1:AvHPZv15LYEe7tZDyFglv7xnbiuF6GMZpZqKpIzXTt0= +go.etcd.io/etcd/api/v3 v3.6.13/go.mod h1:X9+3gaKwzjlOxzo6TZ2u3b7HcHBcAL+Ph7EBPjI/VWk= +go.etcd.io/etcd/client/pkg/v3 v3.6.13 h1:7QeMOisYByx8dBA7/CKcwCaPWfjb5C0xpmrIov/8WyY= +go.etcd.io/etcd/client/pkg/v3 v3.6.13/go.mod h1:Dn2zUBOCu/6xYcd6iAjB7LgoY16OTQjDZfWHLwvuQj4= +go.etcd.io/etcd/client/v3 v3.6.13 h1:0E+9ZYGpMsi9KlOJVoCdONh9PUDawKDTy5mSNY8wOEI= +go.etcd.io/etcd/client/v3 v3.6.13/go.mod h1:rtVI3vwobljb8xlTGcp1Yhz7hBIuBWULXwB848kqJGw= go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= 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/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= 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/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= 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= @@ -655,42 +620,38 @@ 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/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU= -golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/arch v0.28.0 h1:wVwVdqsTuUbJvhYVCspQYwZXHNYeLSoZnmHD+ggddpQ= +golang.org/x/arch v0.28.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/crypto/x509roots/fallback v0.0.0-20260712151947-c1a3b97d708a h1:Xc7UN/F6r6Hfr7Jfl1pe+JDFfHxHrE0U45fZVz28dpo= golang.org/x/crypto/x509roots/fallback v0.0.0-20260712151947-c1a3b97d708a/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs= -golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= -golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= 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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -698,30 +659,24 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210603081109-ebe580a85c40/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -729,21 +684,20 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc 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.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= 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/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= 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= @@ -751,30 +705,22 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= 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-20191204190536-9bdfabe68543/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/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -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/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 h1:jQ9p21COKWjP3VwuFrNRiiOTMh3mPpN45R7SLrH/HUU= +google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 h1:eM/YSd5bBFagF51o1E745Ta7RwzpW0h+z+QDNZOgmQ8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -790,48 +736,45 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/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= -k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= -k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= -k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= -k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= -k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= -k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= +k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= +k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= +k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= +k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= +k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102 h1:xs2ux1MvyrOdfKwS3vuFWrGuLgDOHk6id975Twx2Jss= -k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -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= +k8s.io/kube-openapi v0.0.0-20260624041617-8f3fa4921821 h1:m2wZhD5+vJZyCVkTvUHIfaiXc/mdt3Pxyx3vUnGsKzU= +k8s.io/kube-openapi v0.0.0-20260624041617-8f3fa4921821/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= +modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= +modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= 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/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= +modernc.org/gc/v3 v3.1.3/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/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= +modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= 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/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/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/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= +modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= 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= diff --git a/internal/cli.go b/internal/cli.go new file mode 100644 index 0000000..becf888 --- /dev/null +++ b/internal/cli.go @@ -0,0 +1,143 @@ +package internal + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "os" + "path/filepath" + + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" +) + +type githubCLI struct { + stdout io.Writer + stderr io.Writer + getenv func(string) string +} + +var _ sdk.CLIProvider = (*githubCLI)(nil) + +func NewCLI() sdk.CLIProvider { return newCLI(os.Stdout, os.Stderr) } + +func newCLI(stdout, stderr io.Writer) *githubCLI { + return &githubCLI{stdout: stdout, stderr: stderr, getenv: os.Getenv} +} + +func (c *githubCLI) RunCLI(args []string) int { + if err := c.run(context.Background(), args); err != nil { + _, _ = fmt.Fprintln(c.stderr, err) + return 1 + } + return 0 +} + +func (c *githubCLI) help() error { + _, err := fmt.Fprintln(c.stdout, "Usage: wfctl github runner-policy reconcile [--file policy.json] [--transaction id] [--state-dir directory] [--api-url URL]\n\nplan captures readback and a durable transaction; apply and rollback resume its exact journal. GITHUB_TOKEN is read only from the operator environment.") + return err +} + +func (c *githubCLI) run(ctx context.Context, args []string) error { + if len(args) == 0 || args[0] != "github" { + return errors.New("usage: wfctl github runner-policy reconcile ") + } + if len(args) == 1 { + return c.help() + } + for _, prefix := range []int{1, 2, 3} { + if len(args) == prefix+1 && (args[prefix] == "--help" || args[prefix] == "-h") { + if prefix >= 2 && args[1] != "runner-policy" || prefix >= 3 && args[2] != "reconcile" { + return errors.New("unknown github command") + } + return c.help() + } + } + if len(args) < 4 || args[1] != "runner-policy" || args[2] != "reconcile" { + return errors.New("unknown github command; expected runner-policy reconcile") + } + mode := args[3] + if mode != "plan" && mode != "apply" && mode != "rollback" { + return errors.New("unknown reconcile mode; expected plan, apply, or rollback") + } + fs := flag.NewFlagSet("github runner-policy reconcile", flag.ContinueOnError) + fs.SetOutput(io.Discard) + filePath := fs.String("file", "", "strict desired-state JSON") + tx := fs.String("transaction", "", "durable exact transaction ID") + stateDir := fs.String("state-dir", "", "operator state directory") + apiURL := fs.String("api-url", defaultGitHubAPIBaseURL, "GitHub API base URL") + if err := fs.Parse(args[4:]); err != nil { + if errors.Is(err, flag.ErrHelp) { + return c.help() + } + return errors.New("invalid runner-policy flags; credentials are accepted only through GITHUB_TOKEN") + } + if fs.NArg() != 0 { + return errors.New("unexpected runner-policy positional arguments") + } + if mode == "rollback" && (*tx == "" || *filePath != "") { + return errors.New("rollback requires --transaction and rejects --file") + } + if mode == "plan" && *filePath == "" || mode == "apply" && *filePath == "" && *tx == "" { + return errors.New("plan requires --file; apply requires --file or --transaction") + } + var desired RunnerPolicyDesired + if *filePath != "" { + file, err := os.Open(*filePath) + if err != nil { + return errors.New("policy file could not be opened") + } + desired, err = decodeRunnerPolicy(file) + _ = file.Close() + if err != nil { + return err + } + } + if *tx == "" { + var random [16]byte + if _, err := rand.Read(random[:]); err != nil { + return errors.New("transaction ID could not be generated") + } + *tx = hex.EncodeToString(random[:]) + } + if *stateDir == "" { + base := c.getenv("XDG_STATE_HOME") + if base == "" { + home, err := os.UserHomeDir() + if err != nil { + return errors.New("operator state directory could not be resolved") + } + base = filepath.Join(home, ".local", "state") + } + *stateDir = filepath.Join(base, "wfctl", "plugins", "github") + } + client, err := newRunnerPolicyClient(*apiURL, c.getenv("GITHUB_TOKEN")) + if err != nil { + return err + } + reconciler := newRunnerPolicyReconciler(client, *stateDir) + var result RunnerPolicyResult + if *filePath != "" { + result, err = reconciler.Plan(ctx, *tx, desired) + if err != nil { + return err + } + } + if mode == "apply" { + result, err = reconciler.Apply(ctx, *tx) + } + if mode == "rollback" { + result, err = reconciler.Rollback(ctx, *tx) + } + if err != nil { + return err + } + if err := json.NewEncoder(c.stdout).Encode(result); err != nil { + return errors.New("policy result could not be written") + } + return nil +} diff --git a/internal/cli_launch_test.go b/internal/cli_launch_test.go new file mode 100644 index 0000000..ecfc670 --- /dev/null +++ b/internal/cli_launch_test.go @@ -0,0 +1,263 @@ +package internal + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "strings" + "sync/atomic" + "testing" + "time" +) + +func TestGitHubCLIPublishedWFCTLGlobalLaunch(t *testing.T) { + wfctl, archivePath := os.Getenv("WFCTL_TEST_BINARY"), os.Getenv("GITHUB_TEST_ARCHIVE") + if wfctl == "" || archivePath == "" { + t.Skip("set WFCTL_TEST_BINARY and GITHUB_TEST_ARCHIVE for published-host launch proof") + } + archive, err := os.ReadFile(archivePath) + if err != nil { + t.Fatal(err) + } + reader, err := gzip.NewReader(bytes.NewReader(archive)) + if err != nil { + t.Fatal(err) + } + tarReader := tar.NewReader(reader) + entries := make(map[string]bool) + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + entries[header.Name] = true + } + if err := reader.Close(); err != nil { + t.Fatal(err) + } + if !entries["github"] || !entries["plugin.json"] { + t.Fatal("single-entry archive is not normalized") + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/github.tar.gz" { + http.NotFound(w, r) + return + } + _, _ = w.Write(archive) + })) + defer server.Close() + fake := newPolicyAPIFake(t) + dir := t.TempDir() + env := []string{"PATH=" + os.Getenv("PATH"), "HOME=" + dir, "XDG_STATE_HOME=" + filepath.Join(dir, "state"), "XDG_DATA_HOME=" + filepath.Join(dir, "data"), "WFCTL_GLOBAL_PLUGIN_DIR=" + filepath.Join(dir, "global"), "WFCTL_PLUGIN_DIR=" + filepath.Join(dir, "project-plugins"), "GITHUB_TOKEN=known-private-value", "WFCTL_PLUGIN_INSTALL_QUIET=1", "WFCTL_NO_UPDATE_CHECK=1"} + run := func(args ...string) []byte { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + cmd := exec.CommandContext(ctx, wfctl, args...) + cmd.Dir, cmd.Env = dir, env + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("published wfctl %s failed: %v\n%s", strings.Join(args, " "), err, out) + } + if bytes.Contains(out, []byte("known-private-value")) { + t.Fatal("launch output disclosed credential") + } + t.Logf("wfctl %s\n%s", strings.Join(args, " "), out) + return out + } + if strings.TrimSpace(string(run("version"))) != "v0.86.0" { + t.Fatal("launch proof requires the released wfctl v0.86.0") + } + digest := sha256.Sum256(archive) + install := func() { + run("plugin", "install", "-g", "--url", server.URL+"/github.tar.gz", "--sha256", hex.EncodeToString(digest[:])) + } + install() + for _, path := range []string{"plugin.json", "github"} { + if _, err := os.Stat(filepath.Join(dir, "global", "github", path)); err != nil { + t.Fatal(err) + } + } + if !bytes.Contains(run("github", "--help"), []byte("runner-policy")) { + t.Fatal("global CLI contribution was not launched") + } + data, err := json.Marshal(runnerPolicyFixture()) + if err != nil { + t.Fatal(err) + } + policyPath := filepath.Join(dir, "policy.json") + if err := os.WriteFile(policyPath, data, 0600); err != nil { + t.Fatal(err) + } + run("github", "runner-policy", "reconcile", "plan", "--file", policyPath, "--transaction", "launch-1", "--api-url", fake.server.URL) + run("github", "runner-policy", "reconcile", "apply", "--transaction", "launch-1", "--api-url", fake.server.URL) + fake.mu.Lock() + writes := len(fake.writes) + fake.mu.Unlock() + run("github", "runner-policy", "reconcile", "apply", "--transaction", "launch-1", "--api-url", fake.server.URL) + fake.mu.Lock() + if len(fake.writes) != writes { + fake.mu.Unlock() + t.Fatal("cross-process identical retry mutated GitHub") + } + fake.mu.Unlock() + run("github", "runner-policy", "reconcile", "rollback", "--transaction", "launch-1", "--api-url", fake.server.URL) + fake.mu.Lock() + if fake.variable != nil || len(fake.repoIDs) != 1 || fake.repoIDs[0] != 17 || !fake.group.RestrictedToWorkflows { + fake.mu.Unlock() + t.Fatal("launched rollback did not retain safe restrictions") + } + for _, write := range fake.writes { + if strings.Contains(write, "/runners") || strings.Contains(write, "/runner-groups/1") || strings.Contains(write, "registration-token") { + fake.mu.Unlock() + t.Fatal("launch changed protected state") + } + } + fake.mu.Unlock() + audit, err := os.ReadFile(filepath.Join(dir, "state", "wfctl", "plugins", "github", runnerPolicyAuditFile)) + if err != nil || bytes.Contains(audit, []byte("known-private-value")) { + t.Fatalf("default operator audit missing or unsafe: %v", err) + } + run("plugin", "remove", "-g", "github") + if _, err := os.Stat(filepath.Join(dir, "global", "github")); !os.IsNotExist(err) { + t.Fatal("global remove did not remove the installation") + } + install() + run("github", "--help") + t.Run("ConcurrentTransactions", func(t *testing.T) { + testPublishedPolicyConcurrency(t, wfctl, dir, env, policyPath) + }) +} + +func testPublishedPolicyConcurrency(t *testing.T, wfctl, dir string, env []string, policyPath string) { + t.Helper() + for _, operation := range []string{"plan", "apply", "rollback"} { + t.Run(operation, func(t *testing.T) { + arrived, release := make(chan struct{}, 2), make(chan struct{}) + var gate atomic.Bool + fake := newPolicyAPIFake(t) + fake.server.Close() + fake.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if gate.Load() && r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/actions/variables/WORKFLOW_COMPUTE_STAGING_URL") { + select { + case arrived <- struct{}{}: + default: + } + <-release + } + fake.serve(w, r) + })) + t.Cleanup(fake.server.Close) + var releaseOnce atomic.Bool + unblock := func() { + if releaseOnce.CompareAndSwap(false, true) { + close(release) + } + } + defer unblock() + stateDir := t.TempDir() + auditPath := filepath.Join(stateDir, runnerPolicyAuditFile) + if err := os.WriteFile(auditPath, nil, 0600); err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + run := func(phase string) ([]byte, error) { + args := []string{"github", "runner-policy", "reconcile", phase, "--transaction", "concurrent", "--state-dir", stateDir, "--api-url", fake.server.URL} + if phase == "plan" { + args = append(args, "--file", policyPath) + } + cmd := exec.CommandContext(ctx, wfctl, args...) + cmd.Dir, cmd.Env = dir, env + return cmd.CombinedOutput() + } + check := func(out []byte, err error) { + t.Helper() + if err != nil || bytes.Contains(out, []byte("known-private-value")) { + t.Fatalf("published wfctl transaction failed or disclosed credential: %v\n%s", err, out) + } + t.Logf("published wfctl: %s", out) + } + if operation != "plan" { + check(run("plan")) + } + if operation == "rollback" { + check(run("apply")) + } + gate.Store(true) + type result struct { + out []byte + err error + } + results := make(chan result, 2) + start := func() { + go func() { + out, err := run(operation) + results <- result{out, err} + }() + } + start() + select { + case <-arrived: + case <-ctx.Done(): + t.Fatal("first published CLI did not reach readback barrier") + } + start() + // An unlocked second process reaches the barrier too. A locked one waits + // before loading authority, so release the first after a bounded wait. + select { + case <-arrived: + case <-time.After(2 * time.Second): + } + unblock() + for range 2 { + r := <-results + check(r.out, r.err) + } + gate.Store(false) + audit, err := os.ReadFile(auditPath) + if err != nil { + t.Fatal(err) + } + if plans := bytes.Count(audit, []byte(`"phase":"planned"`)); plans != 1 { + t.Errorf("concurrent published plans recorded %d initial authorities, want 1", plans) + } + if operation == "plan" { + applyOut, applyErr := run("apply") + rollbackOut, rollbackErr := run("rollback") + t.Logf("post-plan apply: %v %s; rollback: %v %s", applyErr, applyOut, rollbackErr, rollbackOut) + check(applyOut, applyErr) + check(rollbackOut, rollbackErr) + } else if operation != "rollback" { + check(run("rollback")) + } + audit, err = os.ReadFile(auditPath) + if err != nil { + t.Fatal(err) + } + for _, phase := range []string{"applied", "rolled-back-restrictive"} { + if count := bytes.Count(audit, []byte(`"phase":"`+phase+`"`)); count != 1 { + t.Fatalf("concurrent %s recorded %d terminal events, want 1", operation, count) + } + } + fake.mu.Lock() + defer fake.mu.Unlock() + if len(fake.writes) != 4 || fake.variable != nil || !fake.group.RestrictedToWorkflows || len(fake.repoIDs) != 1 || fake.repoIDs[0] != 17 { + t.Fatalf("concurrent transaction repeated mutations or failed restrictive rollback: writes=%v", fake.writes) + } + }) + } +} diff --git a/internal/cli_test.go b/internal/cli_test.go new file mode 100644 index 0000000..c24dce5 --- /dev/null +++ b/internal/cli_test.go @@ -0,0 +1,29 @@ +package internal + +import ( + "bytes" + "strings" + "testing" + + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" +) + +func TestGitHubCLIHelpAndStrictDispatch(t *testing.T) { + for _, args := range [][]string{{"github", "--help"}, {"github", "runner-policy", "--help"}, {"github", "runner-policy", "reconcile", "--help"}} { + var out, errs bytes.Buffer + cli := newCLI(&out, &errs) + var provider sdk.CLIProvider = cli + if code := sdk.DispatchArgs(append([]string{"github", "--wfctl-cli"}, args...), NewGitHubPlugin(), provider, nil, nil, &out); code != 0 { + t.Fatalf("CLI help failed: %d: %s", code, errs.String()) + } + if !strings.Contains(out.String(), "runner-policy") || errs.Len() != 0 { + t.Fatalf("unexpected help: %q / %q", out.String(), errs.String()) + } + } + for _, args := range [][]string{nil, {"other"}, {"github", "unknown"}, {"github", "runner-policy", "reconcile", "unknown"}, {"github", "runner-policy", "reconcile", "plan", "--token", "known-private-value"}} { + var out, errs bytes.Buffer + if code := newCLI(&out, &errs).RunCLI(args); code == 0 || strings.Contains(out.String()+errs.String(), "known-private-value") { + t.Fatalf("unsafe dispatch accepted/disclosed: %d: %q / %q", code, out.String(), errs.String()) + } + } +} diff --git a/internal/plugin.go b/internal/plugin.go index ebe22b9..e37e3d5 100644 --- a/internal/plugin.go +++ b/internal/plugin.go @@ -38,7 +38,7 @@ func (p *githubPlugin) Manifest() sdk.PluginManifest { Name: "workflow-plugin-github", Version: Version, Author: "GoCodeAlone", - Description: "GitHub integration plugin: webhook handling, GitHub Actions, PRs, issues, releases, and deployments", + Description: "GitHub integration plugin: webhook handling, GitHub Actions, PRs, issues, releases, deployments, and audited runner policy CLI", } } diff --git a/internal/policy_lock.go b/internal/policy_lock.go new file mode 100644 index 0000000..870bcb3 --- /dev/null +++ b/internal/policy_lock.go @@ -0,0 +1,40 @@ +package internal + +import ( + "context" + "errors" + "os" + "path/filepath" + "time" + + "github.com/gofrs/flock" +) + +const runnerPolicyLockFile = "runner-policy.lock" + +func (r *runnerPolicyReconciler) lockAudit(ctx context.Context, root *os.Root) (*flock.Flock, error) { + file, err := root.OpenFile(runnerPolicyLockFile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600) + if err == nil { + if err := file.Close(); err != nil { + return nil, errors.New("policy lock file could not be closed") + } + } else if !errors.Is(err, os.ErrExist) { + return nil, errors.New("policy lock file could not be created") + } + info, err := root.Lstat(runnerPolicyLockFile) + if err != nil || !info.Mode().IsRegular() || info.Mode().Perm()&0077 != 0 { + return nil, errors.New("policy lock must be an owner-only regular file") + } + // Keep one stable inode across processes and commands. Never remove the + // sidecar: the OS releases its lock when the holder exits, including crashes. + lock := flock.New(filepath.Join(r.dir, runnerPolicyLockFile), flock.SetFlag(os.O_RDWR)) + locked, err := lock.TryLockContext(ctx, 25*time.Millisecond) + if err != nil || !locked { + _ = lock.Close() + if ctx.Err() != nil { + return nil, ctx.Err() + } + return nil, errors.New("policy audit lock could not be acquired") + } + return lock, nil +} diff --git a/internal/policy_lock_test.go b/internal/policy_lock_test.go new file mode 100644 index 0000000..86f1f5c --- /dev/null +++ b/internal/policy_lock_test.go @@ -0,0 +1,81 @@ +package internal + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + "time" +) + +func TestRunnerPolicyLockWaitCancellationAndStableFile(t *testing.T) { + f := newPolicyAPIFake(t) + dir := t.TempDir() + r := policyReconcilerFixture(t, f, dir) + root, err := r.openAudit() + if err != nil { + t.Fatal(err) + } + defer func() { _ = root.Close() }() + lock, err := r.lockAudit(context.Background(), root) + if err != nil { + t.Fatal(err) + } + defer func() { _ = lock.Close() }() + before, err := root.Lstat(runnerPolicyLockFile) + if err != nil || before.Mode().Perm()&0077 != 0 { + t.Fatalf("unsafe lock file: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + other := policyReconcilerFixture(t, f, dir) + if _, err := other.Plan(ctx, "wait", runnerPolicyFixture()); !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("lock wait did not honor cancellation: %v", err) + } + if _, err := root.Stat(runnerPolicyAuditFile); !errors.Is(err, os.ErrNotExist) { + t.Fatal("canceled lock wait appended authority") + } + if err := lock.Close(); err != nil { + t.Fatal(err) + } + if _, err := other.Plan(context.Background(), "wait", runnerPolicyFixture()); err != nil { + t.Fatal(err) + } + after, err := root.Lstat(runnerPolicyLockFile) + if err != nil || !os.SameFile(before, after) { + t.Fatal("unlock/reacquire replaced the shared lock inode") + } +} + +func TestRunnerPolicyUnsafeLockDenied(t *testing.T) { + for _, mode := range []string{"directory", "symlink", "public"} { + t.Run(mode, func(t *testing.T) { + f := newPolicyAPIFake(t) + dir := t.TempDir() + path := filepath.Join(dir, runnerPolicyLockFile) + var err error + switch mode { + case "directory": + err = os.Mkdir(path, 0700) + case "symlink": + err = os.Symlink(filepath.Join(t.TempDir(), "outside"), path) + case "public": + err = os.WriteFile(path, nil, 0600) + if err == nil { + err = os.Chmod(path, 0644) + } + } + if err != nil { + t.Fatal(err) + } + r := policyReconcilerFixture(t, f, dir) + if _, err := r.Plan(context.Background(), "unsafe-lock", runnerPolicyFixture()); err == nil { + t.Fatal("unsafe process lock accepted") + } + if _, err := os.Stat(filepath.Join(dir, runnerPolicyAuditFile)); !errors.Is(err, os.ErrNotExist) || len(f.writes) != 0 { + t.Fatal("lock denial appended authority or mutated GitHub") + } + }) + } +} diff --git a/internal/runner_policy.go b/internal/runner_policy.go new file mode 100644 index 0000000..23cb66d --- /dev/null +++ b/internal/runner_policy.go @@ -0,0 +1,820 @@ +package internal + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "regexp" + "slices" + "strings" + "sync" + "time" + + githubplugin "github.com/GoCodeAlone/workflow-plugin-github" +) + +const runnerPolicySchemaVersion = "github-runner-policy.v1" +const runnerPolicyAuditFile = "runner-policy-audit.jsonl" + +type RunnerPolicyVariable struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type RunnerPolicyDesired struct { + SchemaVersion string `json:"schema_version"` + Organization string `json:"organization"` + RunnerGroup string `json:"runner_group"` + Repository string `json:"repository"` + Workflow string `json:"workflow"` + Visibility string `json:"visibility"` + AllowsPublicRepositories bool `json:"allows_public_repositories"` + RestrictedToWorkflows bool `json:"restricted_to_workflows"` + RunnerIDs []int64 `json:"runner_ids"` + ProtectedRunnerIDs []int64 `json:"protected_runner_ids"` + Variable RunnerPolicyVariable `json:"variable"` +} + +var policyIdentifier = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$`) +var policyVariableName = regexp.MustCompile(`^[A-Z_][A-Z0-9_]{0,99}$`) +var policyWorkflowFile = regexp.MustCompile(`^[A-Za-z0-9_.-]+\.ya?ml$`) +var policyWorkflowRef = regexp.MustCompile(`^refs/(heads|tags)/[A-Za-z0-9_./-]+$`) + +func decodeRunnerPolicy(reader io.Reader) (RunnerPolicyDesired, error) { + var desired RunnerPolicyDesired + data, err := io.ReadAll(io.LimitReader(reader, (1<<20)+1)) + if err != nil || len(data) > 1<<20 { + return desired, errors.New("policy document exceeds the read limit") + } + if err := githubplugin.ValidateRunnerPolicyDocument(data); err != nil { + return desired, errors.New("policy document does not match the strict schema") + } + if err := policyDecodeStrict(bytes.NewReader(data), &desired); err != nil { + return desired, errors.New("policy document is not strict JSON") + } + return desired, desired.validate() +} + +func (d RunnerPolicyDesired) validate() error { + parts := strings.Split(d.Repository, "/") + if d.SchemaVersion != runnerPolicySchemaVersion || !policyIdentifier.MatchString(d.Organization) || !policyIdentifier.MatchString(d.RunnerGroup) || strings.EqualFold(d.RunnerGroup, "Default") || len(parts) != 2 || !strings.EqualFold(parts[0], d.Organization) || !policyIdentifier.MatchString(parts[1]) { + return errors.New("policy requires exact organization, repository, and non-Default runner group") + } + if d.Visibility != "selected" || d.AllowsPublicRepositories || !d.RestrictedToWorkflows || d.RunnerIDs == nil || len(d.RunnerIDs) != 0 { + return errors.New("policy must select one repository and workflow with no public repositories or retained runners") + } + workflow := strings.TrimPrefix(d.Workflow, d.Repository+"/.github/workflows/") + selected := strings.Split(workflow, "@") + if workflow == d.Workflow || len(selected) != 2 || !policyWorkflowFile.MatchString(selected[0]) || selected[0] == ".yml" || selected[0] == ".yaml" || strings.Contains(selected[0], "..") || (!policyWorkflowRef.MatchString(selected[1]) && !isFullGitSHA(selected[1])) || strings.Contains(selected[1], "..") || strings.Contains(selected[1], "//") || strings.HasSuffix(selected[1], "/") { + return errors.New("workflow must identify an exact repository workflow and full ref without wildcards") + } + seen := make(map[int64]bool) + for _, id := range d.ProtectedRunnerIDs { + if id <= 0 || seen[id] { + return errors.New("protected runner IDs must be positive and unique") + } + seen[id] = true + } + if !policyVariableName.MatchString(d.Variable.Name) || !policyPublicURL(d.Variable.Value) { + return errors.New("variable must have a public name and HTTPS URL without credentials, query, or fragment") + } + return nil +} + +func policyPublicURL(value string) bool { + u, err := url.Parse(value) + return err == nil && u.Scheme == "https" && u.Hostname() != "" && u.User == nil && u.RawQuery == "" && !u.ForceQuery && u.Fragment == "" && !strings.ContainsAny(value, "\x00\r\n\t ") +} + +func policyDecodeStrict(reader io.Reader, out any) error { + decoder := json.NewDecoder(reader) + decoder.DisallowUnknownFields() + if err := decoder.Decode(out); err != nil { + return err + } + var trailing json.RawMessage + if err := decoder.Decode(&trailing); err != io.EOF { + return errors.New("trailing JSON data") + } + return nil +} + +type policyGroup struct { + ID int64 `json:"id"` + Name string `json:"name"` + Visibility string `json:"visibility"` + Default bool `json:"default"` + Inherited bool `json:"inherited"` + AllowsPublicRepositories bool `json:"allows_public_repositories"` + RestrictedToWorkflows bool `json:"restricted_to_workflows"` + SelectedWorkflows []string `json:"selected_workflows"` + WorkflowRestrictionsReadOnly bool `json:"workflow_restrictions_read_only"` +} + +type policyGroupPatch struct { + Name string `json:"name"` + Visibility string `json:"visibility"` + AllowsPublicRepositories bool `json:"allows_public_repositories"` + RestrictedToWorkflows bool `json:"restricted_to_workflows"` + SelectedWorkflows []string `json:"selected_workflows"` +} + +type policyRepository struct { + ID int64 `json:"id"` + FullName string `json:"full_name"` + Private bool `json:"private"` +} + +type policyLabel struct { + Name string `json:"name"` +} +type policyRunner struct { + ID int64 `json:"id"` + Name string `json:"name"` + OS string `json:"os"` + Ephemeral bool `json:"ephemeral"` + Labels []policyLabel `json:"labels"` +} +type policyPage struct { + TotalCount int `json:"total_count"` + RunnerGroups []policyGroup `json:"runner_groups"` + Repositories []policyRepository `json:"repositories"` + Runners []policyRunner `json:"runners"` + present map[string]json.RawMessage +} + +func (p *policyPage) UnmarshalJSON(data []byte) error { + type plain policyPage + if err := json.Unmarshal(data, (*plain)(p)); err != nil { + return err + } + if err := json.Unmarshal(data, &p.present); err != nil { + return err + } + if !p.has("total_count") { + return errors.New("page lacks a total count") + } + var groups []json.RawMessage + if err := json.Unmarshal(p.present["runner_groups"], &groups); err != nil && p.has("runner_groups") { + return err + } + for _, group := range groups { + var fields map[string]json.RawMessage + if err := json.Unmarshal(group, &fields); err != nil { + return err + } + for _, name := range []string{"default", "inherited", "allows_public_repositories", "restricted_to_workflows", "workflow_restrictions_read_only"} { + if len(fields[name]) == 0 || bytes.Equal(fields[name], []byte("null")) { + return errors.New("runner group lacks explicit safety fields") + } + } + } + return nil +} + +func (p policyPage) has(name string) bool { + value := p.present[name] + return len(value) != 0 && !bytes.Equal(value, []byte("null")) +} + +type runnerPolicyClient struct { + http *httpGitHubRunnerClient + token string +} + +func newRunnerPolicyClient(baseURL, token string) (*runnerPolicyClient, error) { + u, err := url.Parse(baseURL) + if err != nil || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || (u.Scheme != "https" && (u.Scheme != "http" || !net.ParseIP(u.Hostname()).IsLoopback())) { + return nil, errors.New("GitHub API must use HTTPS or literal loopback without credentials, query, or fragment") + } + if strings.TrimSpace(token) == "" || strings.ContainsAny(token, "\r\n") { + return nil, errors.New("GITHUB_TOKEN is required") + } + return &runnerPolicyClient{http: &httpGitHubRunnerClient{baseURL: strings.TrimRight(baseURL, "/"), httpClient: &http.Client{Timeout: 30 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}}, token: token}, nil +} + +func (c *runnerPolicyClient) request(ctx context.Context, method, path string, body, out any, statuses ...int) (int, error) { + status, _, err := c.send(ctx, method, c.http.baseURL+path, body, out, statuses...) + return status, err +} + +func (c *runnerPolicyClient) send(ctx context.Context, method, endpoint string, body, out any, statuses ...int) (int, http.Header, error) { + var reader io.Reader + if body != nil { + data, err := json.Marshal(body) + if err != nil { + return 0, nil, errors.New("GitHub policy request could not be encoded") + } + reader = bytes.NewReader(data) + } + req, err := http.NewRequestWithContext(ctx, method, endpoint, reader) + if err != nil { + return 0, nil, errors.New("GitHub policy request could not be constructed") + } + req.Header.Set("Authorization", "Bearer "+c.token) + req.Header.Set("Accept", "application/vnd.github+json") + req.Header.Set("X-GitHub-Api-Version", "2026-03-10") + req.Header.Set("User-Agent", "workflow-plugin-github") + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + resp, err := c.http.httpClient.Do(req) + if err != nil { + return 0, nil, errors.New("GitHub policy API transport failed; credential and response omitted") + } + defer func() { _ = resp.Body.Close() }() + if !slices.Contains(statuses, resp.StatusCode) { + return resp.StatusCode, nil, fmt.Errorf("GitHub policy API returned HTTP %d", resp.StatusCode) + } + if out != nil && resp.StatusCode != http.StatusNotFound { + data, err := io.ReadAll(io.LimitReader(resp.Body, (1<<20)+1)) + if err != nil || len(data) > 1<<20 || json.Unmarshal(data, out) != nil { + return resp.StatusCode, nil, errors.New("GitHub policy API response is incomplete, invalid, or exceeds its bound") + } + } + return resp.StatusCode, resp.Header, nil +} + +func (c *runnerPolicyClient) pages(ctx context.Context, path, kind string) (policyPage, error) { + var all policyPage + guard := githubPaginationGuard{} + endpoint := c.http.baseURL + path + "?per_page=100" + for endpoint != "" { + if err := guard.visit(endpoint); err != nil { + return all, errors.New("GitHub policy pagination is cyclic or exceeds its bound") + } + var page policyPage + _, header, err := c.send(ctx, http.MethodGet, endpoint, nil, &page, http.StatusOK) + if err != nil { + return all, errors.New("GitHub policy page could not be read") + } + field := map[string]string{"groups": "runner_groups", "repositories": "repositories", "runners": "runners"}[kind] + if !page.has(field) { + return all, errors.New("GitHub policy page lacks an explicit collection") + } + if guard.pages == 1 { + all.TotalCount = page.TotalCount + } + if page.TotalCount < 0 || page.TotalCount != all.TotalCount { + return all, errors.New("GitHub policy pagination count changed") + } + all.RunnerGroups = append(all.RunnerGroups, page.RunnerGroups...) + all.Repositories = append(all.Repositories, page.Repositories...) + all.Runners = append(all.Runners, page.Runners...) + endpoint, err = c.http.nextPage(header.Get("Link")) + if err != nil { + return all, errors.New("GitHub policy pagination changed origin") + } + if endpoint != "" { + u, err := url.Parse(endpoint) + original, parseErr := url.Parse(c.http.baseURL + path) + if err != nil || parseErr != nil || u.Path != original.Path || u.Fragment != "" { + return all, errors.New("GitHub policy pagination changed collection") + } + } + } + count := 0 + switch kind { + case "groups": + count = len(all.RunnerGroups) + case "repositories": + count = len(all.Repositories) + case "runners": + count = len(all.Runners) + } + if count != all.TotalCount { + return all, errors.New("GitHub policy pagination did not prove a complete list") + } + return all, nil +} + +type policyGroupState struct { + Group policyGroup `json:"group"` + RepositoryIDs []int64 `json:"repository_ids"` + RunnerIDs []int64 `json:"runner_ids"` +} +type policySnapshot struct { + RepositoryID int64 `json:"repository_id"` + Target policyGroupState `json:"target"` + ProtectedHash string `json:"protected_hash"` + Variable *RunnerPolicyVariable `json:"variable"` +} + +type policyRunnerEvidence struct { + ID int64 `json:"id"` + Hash string `json:"metadata_hash"` +} + +func (c *runnerPolicyClient) snapshot(ctx context.Context, d RunnerPolicyDesired, expectedID int64) (policySnapshot, error) { + var state policySnapshot + var repo policyRepository + if _, err := c.request(ctx, http.MethodGet, "/repos/"+d.Repository, nil, &repo, http.StatusOK); err != nil { + return state, err + } + if repo.ID <= 0 || !strings.EqualFold(repo.FullName, d.Repository) || !repo.Private { + return state, errors.New("selected repository must resolve exactly and be private") + } + state.RepositoryID = repo.ID + orgPath := "/orgs/" + d.Organization + "/actions" + groups, err := c.pages(ctx, orgPath+"/runner-groups", "groups") + if err != nil { + return state, err + } + slices.SortFunc(groups.RunnerGroups, func(a, b policyGroup) int { return cmpPolicyID(a.ID, b.ID) }) + var protected []policyGroupState + seen := make(map[int64]bool) + found := false + for _, group := range groups.RunnerGroups { + if group.ID <= 0 || seen[group.ID] { + return state, errors.New("runner groups have missing or duplicate IDs") + } + seen[group.ID] = true + group.SelectedWorkflows = append([]string{}, group.SelectedWorkflows...) + slices.Sort(group.SelectedWorkflows) + groupPath := fmt.Sprintf("%s/runner-groups/%d", orgPath, group.ID) + runners, err := c.pages(ctx, groupPath+"/runners", "runners") + if err != nil { + return state, err + } + gs := policyGroupState{Group: group, RepositoryIDs: []int64{}, RunnerIDs: []int64{}} + for _, runner := range runners.Runners { + gs.RunnerIDs = append(gs.RunnerIDs, runner.ID) + } + slices.Sort(gs.RunnerIDs) + if !policyUniqueIDs(gs.RunnerIDs) { + return state, errors.New("group runner IDs are missing or duplicated") + } + if group.Visibility == "selected" { + repos, err := c.pages(ctx, groupPath+"/repositories", "repositories") + if err != nil { + return state, err + } + for _, repository := range repos.Repositories { + gs.RepositoryIDs = append(gs.RepositoryIDs, repository.ID) + } + slices.Sort(gs.RepositoryIDs) + if !policyUniqueIDs(gs.RepositoryIDs) { + return state, errors.New("selected repository IDs are missing or duplicated") + } + } + if group.Name == d.RunnerGroup { + if found || group.Default || strings.EqualFold(group.Name, "Default") || group.Inherited || group.WorkflowRestrictionsReadOnly || len(gs.RunnerIDs) != 0 || (expectedID != 0 && group.ID != expectedID) { + return state, errors.New("target runner group is ambiguous, protected, read-only, changed identity, or contains retained runners") + } + state.Target, found = gs, true + } else { + protected = append(protected, gs) + } + } + if !found { + return state, errors.New("exact runner group was not found") + } + runners, err := c.pages(ctx, orgPath+"/runners", "runners") + if err != nil { + return state, err + } + slices.SortFunc(runners.Runners, func(a, b policyRunner) int { return cmpPolicyID(a.ID, b.ID) }) + ids := make([]int64, 0, len(runners.Runners)) + var evidence []policyRunnerEvidence + for _, runner := range runners.Runners { + ids = append(ids, runner.ID) + slices.SortFunc(runner.Labels, func(a, b policyLabel) int { return strings.Compare(a.Name, b.Name) }) + hash, err := policyHash(runner) + if err != nil { + return state, err + } + evidence = append(evidence, policyRunnerEvidence{runner.ID, hash}) + } + if !policyUniqueIDs(ids) { + return state, errors.New("organization runner IDs are missing or duplicated") + } + for _, id := range d.ProtectedRunnerIDs { + if !slices.Contains(ids, id) { + return state, errors.New("declared protected runner is absent") + } + } + state.ProtectedHash, err = policyHash(struct { + Groups []policyGroupState `json:"groups"` + Runners []policyRunnerEvidence `json:"runners"` + }{protected, evidence}) + if err != nil { + return state, err + } + var variable RunnerPolicyVariable + status, err := c.request(ctx, http.MethodGet, "/repos/"+d.Repository+"/actions/variables/"+d.Variable.Name, nil, &variable, http.StatusOK, http.StatusNotFound) + if err != nil { + return state, err + } + if status == http.StatusOK { + if variable.Name != d.Variable.Name || !policyPublicURL(variable.Value) { + return state, errors.New("existing variable is not exact public URL state; refusing to journal its bytes") + } + state.Variable = &variable + } + return state, nil +} + +func cmpPolicyID(a, b int64) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} +func policyUniqueIDs(ids []int64) bool { + for i, id := range ids { + if id <= 0 || (i > 0 && ids[i-1] == id) { + return false + } + } + return true +} +func policyHash(value any) (string, error) { + data, err := json.Marshal(value) + if err != nil { + return "", errors.New("policy state could not be encoded") + } + sum := sha256.Sum256(data) + return "sha256:" + hex.EncodeToString(sum[:]), nil +} +func policySame(a, b policySnapshot) bool { + x, err := policyHash(a) + if err != nil { + return false + } + y, err := policyHash(b) + return err == nil && x == y +} + +type runnerPolicyEvent struct { + SchemaVersion string `json:"schema_version"` + TransactionID string `json:"transaction_id"` + Phase string `json:"phase"` + Step int `json:"step"` + Time time.Time `json:"time"` + Desired *RunnerPolicyDesired `json:"desired,omitempty"` + Before *policySnapshot `json:"before,omitempty"` + After *policySnapshot `json:"after,omitempty"` + BeforeHash string `json:"before_hash"` + AfterHash string `json:"after_hash,omitempty"` +} + +type RunnerPolicyResult struct { + TransactionID string `json:"transaction_id"` + Phase string `json:"phase"` + BeforeHash string `json:"before_hash"` + AfterHash string `json:"after_hash,omitempty"` +} + +type runnerPolicyReconciler struct { + client *runnerPolicyClient + dir string + mu sync.Mutex +} + +func newRunnerPolicyReconciler(client *runnerPolicyClient, dir string) *runnerPolicyReconciler { + return &runnerPolicyReconciler{client: client, dir: dir} +} + +func (r *runnerPolicyReconciler) openAudit() (*os.Root, error) { + if err := os.MkdirAll(r.dir, 0700); err != nil { + return nil, errors.New("policy state directory could not be created") + } + info, err := os.Lstat(r.dir) + if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 || info.Mode().Perm()&0022 != 0 { + return nil, errors.New("policy state directory must be an owner-controlled directory") + } + root, err := os.OpenRoot(r.dir) + if err != nil { + return nil, errors.New("policy state directory could not be opened") + } + info, err = root.Lstat(runnerPolicyAuditFile) + if err != nil && !errors.Is(err, os.ErrNotExist) { + _ = root.Close() + return nil, errors.New("policy audit could not be inspected") + } + if err == nil && (!info.Mode().IsRegular() || info.Mode().Perm()&0077 != 0) { + _ = root.Close() + return nil, errors.New("policy audit must be an owner-only regular file") + } + return root, nil +} + +func (r *runnerPolicyReconciler) append(root *os.Root, event runnerPolicyEvent) error { + event.SchemaVersion, event.Time = runnerPolicySchemaVersion, time.Now().UTC() + data, err := json.Marshal(event) + if err != nil || bytes.Contains(data, []byte(r.client.token)) { + return errors.New("policy audit contains invalid or credential-bearing state") + } + file, err := root.OpenFile(runnerPolicyAuditFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return errors.New("policy audit could not be opened for append") + } + n, writeErr := file.Write(append(data, '\n')) + syncErr := file.Sync() + closeErr := file.Close() + if n != len(data)+1 || errors.Join(writeErr, syncErr, closeErr) != nil { + return errors.New("policy audit append was not durable") + } + if err := syncJITOwnershipJournalDirectoryPlatform(root, r.dir); err != nil { + return errors.New("policy audit directory sync failed") + } + return nil +} + +func (r *runnerPolicyReconciler) load(root *os.Root, tx string) (runnerPolicyEvent, runnerPolicyEvent, error) { + var plan, last runnerPolicyEvent + file, err := root.Open(runnerPolicyAuditFile) + if errors.Is(err, os.ErrNotExist) { + return plan, last, nil + } + if err != nil { + return plan, last, errors.New("policy audit could not be read") + } + defer func() { _ = file.Close() }() + data, err := io.ReadAll(io.LimitReader(file, (64<<20)+1)) + if err != nil || len(data) > 64<<20 || (len(data) > 0 && data[len(data)-1] != '\n') { + return plan, last, errors.New("policy audit is incomplete or exceeds its read bound") + } + for line := range bytes.SplitSeq(data, []byte{'\n'}) { + if len(line) == 0 { + continue + } + var event runnerPolicyEvent + if err := policyDecodeStrict(bytes.NewReader(line), &event); err != nil || event.SchemaVersion != runnerPolicySchemaVersion { + return plan, last, errors.New("policy audit is malformed") + } + if event.TransactionID != tx { + continue + } + if event.Phase == "planned" { + if plan.Desired != nil || event.Desired == nil || event.Before == nil { + return plan, last, errors.New("policy transaction has invalid initial authority") + } + plan = event + } + last = event + } + return plan, last, nil +} + +func (r *runnerPolicyReconciler) Plan(ctx context.Context, tx string, desired RunnerPolicyDesired) (RunnerPolicyResult, error) { + r.mu.Lock() + defer r.mu.Unlock() + if !policyIdentifier.MatchString(tx) { + return RunnerPolicyResult{}, errors.New("transaction ID must be an exact identifier") + } + if err := desired.validate(); err != nil { + return RunnerPolicyResult{}, err + } + root, err := r.openAudit() + if err != nil { + return RunnerPolicyResult{}, err + } + defer func() { _ = root.Close() }() + lock, err := r.lockAudit(ctx, root) + if err != nil { + return RunnerPolicyResult{}, err + } + defer func() { _ = lock.Close() }() + plan, last, err := r.load(root, tx) + if err != nil { + return RunnerPolicyResult{}, err + } + if plan.Desired != nil { + a, _ := policyHash(desired) + b, _ := policyHash(*plan.Desired) + if a != b { + return RunnerPolicyResult{}, errors.New("transaction ID already binds different desired state") + } + return policyEventResult(last), nil + } + before, err := r.client.snapshot(ctx, desired, 0) + if err != nil { + return RunnerPolicyResult{}, err + } + if !policyDesiredNarrows(desired, before) { + return RunnerPolicyResult{}, errors.New("desired policy would broaden current repository or workflow access") + } + hash, err := policyHash(before) + if err != nil { + return RunnerPolicyResult{}, err + } + plan = runnerPolicyEvent{TransactionID: tx, Phase: "planned", Desired: &desired, Before: &before, BeforeHash: hash} + if err := r.append(root, plan); err != nil { + return RunnerPolicyResult{}, err + } + return policyEventResult(plan), nil +} + +func policyDesiredNarrows(d RunnerPolicyDesired, before policySnapshot) bool { + g := before.Target.Group + if g.Visibility == "selected" && !slices.Contains(before.Target.RepositoryIDs, before.RepositoryID) { + return false + } + if g.Visibility != "selected" && g.Visibility != "all" && g.Visibility != "private" { + return false + } + return !g.RestrictedToWorkflows || slices.Contains(g.SelectedWorkflows, d.Workflow) +} + +func policyEventResult(event runnerPolicyEvent) RunnerPolicyResult { + return RunnerPolicyResult{TransactionID: event.TransactionID, Phase: event.Phase, BeforeHash: event.BeforeHash, AfterHash: event.AfterHash} +} + +func policyDesiredState(before policySnapshot, d RunnerPolicyDesired) policySnapshot { + next := before + next.Target.Group.Visibility = "selected" + next.Target.Group.AllowsPublicRepositories = false + next.Target.Group.RestrictedToWorkflows = true + next.Target.Group.SelectedWorkflows = []string{d.Workflow} + next.Target.RepositoryIDs = []int64{before.RepositoryID} + next.Variable = &d.Variable + return next +} + +func (r *runnerPolicyReconciler) Apply(ctx context.Context, tx string) (RunnerPolicyResult, error) { + return r.reconcile(ctx, tx, false) +} +func (r *runnerPolicyReconciler) Rollback(ctx context.Context, tx string) (RunnerPolicyResult, error) { + return r.reconcile(ctx, tx, true) +} + +func (r *runnerPolicyReconciler) reconcile(ctx context.Context, tx string, rollback bool) (RunnerPolicyResult, error) { + r.mu.Lock() + defer r.mu.Unlock() + if !policyIdentifier.MatchString(tx) { + return RunnerPolicyResult{}, errors.New("transaction ID must be an exact identifier") + } + root, err := r.openAudit() + if err != nil { + return RunnerPolicyResult{}, err + } + defer func() { _ = root.Close() }() + lock, err := r.lockAudit(ctx, root) + if err != nil { + return RunnerPolicyResult{}, err + } + defer func() { _ = lock.Close() }() + plan, last, err := r.load(root, tx) + if err != nil { + return RunnerPolicyResult{}, err + } + if plan.Desired == nil || plan.Before == nil { + return RunnerPolicyResult{}, errors.New("transaction has no durable plan") + } + d := *plan.Desired + if err := d.validate(); err != nil { + return RunnerPolicyResult{}, errors.New("stored policy is invalid") + } + current, err := r.client.snapshot(ctx, d, plan.Before.Target.Group.ID) + if err != nil { + return RunnerPolicyResult{}, err + } + if current.ProtectedHash != plan.Before.ProtectedHash || current.RepositoryID != plan.Before.RepositoryID { + return RunnerPolicyResult{}, errors.New("protected groups, runner metadata, or repository identity changed") + } + if last.Phase == "applied" && !rollback || strings.HasPrefix(last.Phase, "rolled-back") { + if last.After == nil || !policySame(current, *last.After) { + return RunnerPolicyResult{}, errors.New("terminal policy readback changed") + } + if strings.HasPrefix(last.Phase, "rolled-back") && !rollback { + return RunnerPolicyResult{}, errors.New("rolled-back transaction cannot be reapplied") + } + return policyEventResult(last), nil + } + var expected policySnapshot + switch last.Phase { + case "planned": + expected = *plan.Before + case "write-intent", "rollback-intent": + if last.Before == nil || last.After == nil || (!policySame(current, *last.Before) && !policySame(current, *last.After)) { + return RunnerPolicyResult{}, errors.New("interrupted write has unrecognized readback") + } + expected = current + case "write-ack", "rollback-ack", "applied": + if last.After == nil { + return RunnerPolicyResult{}, errors.New("policy acknowledgement is missing readback") + } + expected = *last.After + default: + return RunnerPolicyResult{}, errors.New("policy journal phase is not resumable") + } + if !policySame(current, expected) { + return RunnerPolicyResult{}, errors.New("policy changed since the recorded plan or acknowledgement") + } + if !rollback && strings.HasPrefix(last.Phase, "rollback-") { + return RunnerPolicyResult{}, errors.New("rollback transaction cannot resume apply") + } + wanted := policyDesiredState(*plan.Before, d) + phase, intent, ack := "applied", "write-intent", "write-ack" + if rollback { + wanted = current + wanted.Variable = plan.Before.Variable + phase, intent, ack = "rolled-back-restrictive", "rollback-intent", "rollback-ack" + // Restoring a broad captured ACL would revoke the safety restriction. + // Keep that ACL restrictive; only restore ACLs proven to be a subset. + if policyACLSubset(plan.Before.Target, current.Target) { + wanted.Target = plan.Before.Target + phase = "rolled-back" + } + } + for step := 1; step <= 3; step++ { + next := current + switch step { + case 1: + next.Target.Group = wanted.Target.Group + case 2: + next.Target.RepositoryIDs = wanted.Target.RepositoryIDs + case 3: + next.Variable = wanted.Variable + } + beforeHash, _ := policyHash(current) + afterHash, _ := policyHash(next) + event := runnerPolicyEvent{TransactionID: tx, Phase: intent, Step: step, Before: ¤t, After: &next, BeforeHash: beforeHash, AfterHash: afterHash} + if beforeHash != afterHash { + if err := r.append(root, event); err != nil { + return RunnerPolicyResult{}, err + } + writeErr := r.write(ctx, d, current, next, step) + observed, readErr := r.client.snapshot(ctx, d, plan.Before.Target.Group.ID) + if readErr != nil || !policySame(observed, next) { + if writeErr != nil { + return RunnerPolicyResult{}, errors.New("GitHub write was not confirmed; retry the exact transaction") + } + return RunnerPolicyResult{}, errors.New("GitHub policy write readback does not equal the exact intended state") + } + current = observed + } else { + current = next + } + event.Phase = ack + event.After = ¤t + if err := r.append(root, event); err != nil { + return RunnerPolicyResult{}, err + } + } + afterHash, _ := policyHash(current) + terminal := runnerPolicyEvent{TransactionID: tx, Phase: phase, BeforeHash: plan.BeforeHash, AfterHash: afterHash, After: ¤t} + if err := r.append(root, terminal); err != nil { + return RunnerPolicyResult{}, err + } + return policyEventResult(terminal), nil +} + +func policyACLSubset(before, current policyGroupState) bool { + if policyGroupACLHash(before) == policyGroupACLHash(current) { + return true + } + if before.Group.Visibility != "selected" || before.Group.AllowsPublicRepositories || !before.Group.RestrictedToWorkflows || current.Group.Visibility != "selected" || !current.Group.RestrictedToWorkflows { + return false + } + for _, id := range before.RepositoryIDs { + if !slices.Contains(current.RepositoryIDs, id) { + return false + } + } + for _, workflow := range before.Group.SelectedWorkflows { + if !slices.Contains(current.Group.SelectedWorkflows, workflow) { + return false + } + } + return true +} + +func policyGroupACLHash(state policyGroupState) string { hash, _ := policyHash(state); return hash } + +func (r *runnerPolicyReconciler) write(ctx context.Context, d RunnerPolicyDesired, before, after policySnapshot, step int) error { + groupPath := fmt.Sprintf("/orgs/%s/actions/runner-groups/%d", d.Organization, before.Target.Group.ID) + var err error + switch step { + case 1: + g := after.Target.Group + _, err = r.client.request(ctx, http.MethodPatch, groupPath, policyGroupPatch{g.Name, g.Visibility, g.AllowsPublicRepositories, g.RestrictedToWorkflows, g.SelectedWorkflows}, nil, http.StatusOK) + case 2: + _, err = r.client.request(ctx, http.MethodPut, groupPath+"/repositories", struct { + IDs []int64 `json:"selected_repository_ids"` + }{after.Target.RepositoryIDs}, nil, http.StatusNoContent) + case 3: + path := "/repos/" + d.Repository + "/actions/variables" + if after.Variable == nil { + _, err = r.client.request(ctx, http.MethodDelete, path+"/"+d.Variable.Name, nil, nil, http.StatusNoContent, http.StatusNotFound) + } else if before.Variable == nil { + _, err = r.client.request(ctx, http.MethodPost, path, after.Variable, nil, http.StatusCreated) + } else { + _, err = r.client.request(ctx, http.MethodPatch, path+"/"+d.Variable.Name, after.Variable, nil, http.StatusNoContent) + } + } + return err +} diff --git a/internal/runner_policy_test.go b/internal/runner_policy_test.go new file mode 100644 index 0000000..fa14518 --- /dev/null +++ b/internal/runner_policy_test.go @@ -0,0 +1,473 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + "testing" +) + +func runnerPolicyFixture() RunnerPolicyDesired { + return RunnerPolicyDesired{ + SchemaVersion: "github-runner-policy.v1", Organization: "GoCodeAlone", RunnerGroup: "ephemeral", + Repository: "GoCodeAlone/workflow-compute", Workflow: "GoCodeAlone/workflow-compute/.github/workflows/dogfood-provider-target.yml@refs/heads/main", + Visibility: "selected", AllowsPublicRepositories: false, RestrictedToWorkflows: true, + RunnerIDs: []int64{}, ProtectedRunnerIDs: []int64{279, 280, 63}, + Variable: RunnerPolicyVariable{Name: "WORKFLOW_COMPUTE_STAGING_URL", Value: "https://staging.example.test"}, + } +} + +type policyAPIFake struct { + mu sync.Mutex + server *httptest.Server + group policyGroup + repoIDs []int64 + variable *RunnerPolicyVariable + retained bool + loseOnce string + writes []string + crossOrigin string + badReadback bool + missingList bool + missingSafety bool + variableFailure bool + interruptWrite string + beforeWrite bool + blockReads bool +} + +func newPolicyAPIFake(t *testing.T) *policyAPIFake { + t.Helper() + f := &policyAPIFake{ + group: policyGroup{ID: 2, Name: "ephemeral", Visibility: "selected", SelectedWorkflows: []string{}}, + repoIDs: []int64{17, 18}, + } + f.server = httptest.NewServer(http.HandlerFunc(f.serve)) + t.Cleanup(f.server.Close) + return f +} + +func (f *policyAPIFake) serve(w http.ResponseWriter, r *http.Request) { + f.mu.Lock() + defer f.mu.Unlock() + if r.Header.Get("Authorization") != "Bearer known-private-value" { + w.WriteHeader(401) + return + } + path := r.URL.Path + if r.Method == "GET" && f.blockReads { + w.WriteHeader(http.StatusServiceUnavailable) + return + } + page2 := r.URL.Query().Get("page") == "2" + writeJSON := func(v any) { w.Header().Set("Content-Type", "application/json"); _ = json.NewEncoder(w).Encode(v) } + runner := func(id int64) policyRunner { + return policyRunner{ID: id, Name: fmt.Sprintf("runner-%d", id), OS: "linux", Labels: []policyLabel{{Name: "self-hosted"}}} + } + if r.Method != "GET" { + f.writes = append(f.writes, r.Method+" "+path) + if f.interruptWrite == r.Method+" "+path { + f.blockReads = true + if f.beforeWrite { + w.WriteHeader(http.StatusServiceUnavailable) + return + } + } + switch { + case r.Method == "PATCH" && path == "/orgs/GoCodeAlone/actions/runner-groups/2": + var patch struct { + Name string `json:"name"` + policyGroupPatch + } + if json.NewDecoder(r.Body).Decode(&patch) != nil { + w.WriteHeader(400) + return + } + if patch.Name == "" || patch.Name != f.group.Name { + w.WriteHeader(http.StatusUnprocessableEntity) + return + } + f.group.Visibility, f.group.AllowsPublicRepositories, f.group.RestrictedToWorkflows, f.group.SelectedWorkflows = patch.Visibility, patch.AllowsPublicRepositories, patch.RestrictedToWorkflows, patch.SelectedWorkflows + case r.Method == "PUT" && path == "/orgs/GoCodeAlone/actions/runner-groups/2/repositories": + var put struct { + IDs []int64 `json:"selected_repository_ids"` + } + if json.NewDecoder(r.Body).Decode(&put) != nil { + w.WriteHeader(400) + return + } + f.repoIDs = put.IDs + case (r.Method == "POST" && path == "/repos/GoCodeAlone/workflow-compute/actions/variables") || (r.Method == "PATCH" && path == "/repos/GoCodeAlone/workflow-compute/actions/variables/WORKFLOW_COMPUTE_STAGING_URL"): + var variable RunnerPolicyVariable + if json.NewDecoder(r.Body).Decode(&variable) != nil { + w.WriteHeader(400) + return + } + f.variable = &variable + case r.Method == "DELETE" && path == "/repos/GoCodeAlone/workflow-compute/actions/variables/WORKFLOW_COMPUTE_STAGING_URL": + f.variable = nil + default: + w.WriteHeader(500) + _, _ = w.Write([]byte("known-private-value forbidden mutation")) + return + } + if f.loseOnce == r.Method+" "+path { + f.loseOnce = "" + conn, _, err := w.(http.Hijacker).Hijack() + if err == nil { + _ = conn.Close() + } + return + } + if r.Method == "PATCH" && strings.Contains(path, "runner-groups") { + writeJSON(f.group) + return + } + if r.Method == "POST" { + w.WriteHeader(201) + } else { + w.WriteHeader(204) + } + return + } + switch path { + case "/repos/GoCodeAlone/workflow-compute": + writeJSON(policyRepository{ID: 17, FullName: "GoCodeAlone/workflow-compute", Private: true}) + case "/orgs/GoCodeAlone/actions/runner-groups": + if !page2 { + link := f.server.URL + path + "?page=2" + if f.crossOrigin != "" { + link = f.crossOrigin + } + w.Header().Set("Link", "<"+link+">; rel=\"next\"") + writeJSON(policyPage{TotalCount: 2, RunnerGroups: []policyGroup{{ID: 1, Name: "Default", Visibility: "all", Default: true, SelectedWorkflows: []string{}}}}) + } else if f.missingSafety { + data, _ := json.Marshal(f.group) + data = bytes.Replace(data, []byte(`"inherited":false,`), nil, 1) + _, _ = fmt.Fprintf(w, `{"total_count":2,"runner_groups":[%s]}`, data) + } else { + writeJSON(policyPage{TotalCount: 2, RunnerGroups: []policyGroup{f.group}}) + } + case "/orgs/GoCodeAlone/actions/runner-groups/2": + writeJSON(f.group) + case "/orgs/GoCodeAlone/actions/runner-groups/2/repositories": + repos := make([]policyRepository, 0, len(f.repoIDs)) + for _, id := range f.repoIDs { + repos = append(repos, policyRepository{ID: id}) + } + if f.badReadback && len(f.writes) > 0 { + repos = append(repos, policyRepository{ID: 999}) + } + writeJSON(policyPage{TotalCount: len(repos), Repositories: repos}) + case "/orgs/GoCodeAlone/actions/runner-groups/1/runners", "/orgs/GoCodeAlone/actions/runners": + if !page2 { + w.Header().Set("Link", "<"+f.server.URL+path+"?page=2>; rel=\"next\"") + writeJSON(policyPage{TotalCount: 3, Runners: []policyRunner{runner(279), runner(280)}}) + } else { + writeJSON(policyPage{TotalCount: 3, Runners: []policyRunner{runner(63)}}) + } + case "/orgs/GoCodeAlone/actions/runner-groups/2/runners": + if f.missingList { + _, _ = w.Write([]byte(`{"total_count":0}`)) + return + } + if f.retained { + writeJSON(policyPage{TotalCount: 1, Runners: []policyRunner{runner(63)}}) + } else { + writeJSON(policyPage{TotalCount: 0, Runners: []policyRunner{}}) + } + case "/repos/GoCodeAlone/workflow-compute/actions/variables/WORKFLOW_COMPUTE_STAGING_URL": + if f.variableFailure { + w.WriteHeader(500) + _, _ = w.Write([]byte("known-private-value")) + return + } + if f.variable == nil { + w.WriteHeader(404) + } else { + writeJSON(f.variable) + } + default: + w.WriteHeader(404) + } +} + +func policyReconcilerFixture(t *testing.T, f *policyAPIFake, dir string) *runnerPolicyReconciler { + t.Helper() + client, err := newRunnerPolicyClient(f.server.URL, "known-private-value") + if err != nil { + t.Fatal(err) + } + return newRunnerPolicyReconciler(client, dir) +} + +func TestRunnerPolicyGroupPatchPreservesRequiredName(t *testing.T) { + f := newPolicyAPIFake(t) + f.group.Name = "Ephemeral" + desired := runnerPolicyFixture() + desired.RunnerGroup = f.group.Name + r := policyReconcilerFixture(t, f, t.TempDir()) + if _, err := r.Plan(context.Background(), "required-name", desired); err != nil { + t.Fatal(err) + } + if _, err := r.Apply(context.Background(), "required-name"); err != nil { + t.Fatalf("PATCH must include the captured unchanged group name: %v", err) + } + if _, err := r.Rollback(context.Background(), "required-name"); err != nil { + t.Fatal(err) + } + f.mu.Lock() + defer f.mu.Unlock() + if f.group.Name != "Ephemeral" { + t.Fatal("policy mutation renamed the runner group") + } +} + +func TestRunnerPolicyDuplicateInitialAuthorityDenied(t *testing.T) { + f := newPolicyAPIFake(t) + dir := t.TempDir() + r := policyReconcilerFixture(t, f, dir) + desired := runnerPolicyFixture() + if _, err := r.Plan(context.Background(), "duplicate", desired); err != nil { + t.Fatal(err) + } + path := filepath.Join(dir, runnerPolicyAuditFile) + data, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, append(data, data...), 0600); err != nil { + t.Fatal(err) + } + for _, operation := range []func() (RunnerPolicyResult, error){ + func() (RunnerPolicyResult, error) { return r.Plan(context.Background(), "duplicate", desired) }, + func() (RunnerPolicyResult, error) { return r.Apply(context.Background(), "duplicate") }, + func() (RunnerPolicyResult, error) { return r.Rollback(context.Background(), "duplicate") }, + } { + if _, err := operation(); err == nil || !strings.Contains(err.Error(), "invalid initial authority") { + t.Fatalf("duplicate initial authority was not denied: %v", err) + } + } + if len(f.writes) != 0 { + t.Fatal("duplicate authority denial mutated GitHub") + } +} + +func TestRunnerPolicyStrictSchemaAndBroadeningDenial(t *testing.T) { + desired := runnerPolicyFixture() + data, _ := json.Marshal(desired) + for _, mutate := range []func([]byte) []byte{ + func(d []byte) []byte { return append(d, []byte(` {}`)...) }, + func(d []byte) []byte { + return bytes.Replace(d, []byte(`"visibility":"selected"`), []byte(`"visibility":"all"`), 1) + }, + func(d []byte) []byte { + return bytes.Replace(d, []byte(`"restricted_to_workflows":true`), []byte(`"restricted_to_workflows":false`), 1) + }, + func(d []byte) []byte { + return bytes.Replace(d, []byte(`"runner_ids":[]`), []byte(`"runner_ids":[63]`), 1) + }, + func(d []byte) []byte { + return bytes.Replace(d, []byte(`"runner_group":"ephemeral"`), []byte(`"runner_group":"Default"`), 1) + }, + func(d []byte) []byte { + return bytes.Replace(d, []byte(`"variable":{`), []byte(`"variable":{"token":"known-private-value",`), 1) + }, + func(d []byte) []byte { return bytes.Replace(d, []byte(`@refs/heads/main`), []byte(`@refs/heads/*`), 1) }, + } { + if _, err := decodeRunnerPolicy(bytes.NewReader(mutate(bytes.Clone(data)))); err == nil || strings.Contains(err.Error(), "known-private-value") { + t.Fatalf("unsafe policy accepted/disclosed: %v", err) + } + } + if _, err := decodeRunnerPolicy(bytes.NewReader(data)); err != nil { + t.Fatal(err) + } +} + +func TestRunnerPolicyPaginationApplyRetryRollbackAndAudit(t *testing.T) { + for _, lost := range []string{"", "PATCH /orgs/GoCodeAlone/actions/runner-groups/2", "PUT /orgs/GoCodeAlone/actions/runner-groups/2/repositories", "POST /repos/GoCodeAlone/workflow-compute/actions/variables"} { + t.Run(lost, func(t *testing.T) { + f := newPolicyAPIFake(t) + f.loseOnce = lost + dir := t.TempDir() + r := policyReconcilerFixture(t, f, dir) + if _, err := r.Plan(context.Background(), "transaction-1", runnerPolicyFixture()); err != nil { + t.Fatal(err) + } + if len(f.writes) != 0 { + t.Fatal("plan mutated GitHub") + } + result, err := r.Apply(context.Background(), "transaction-1") + if err != nil { + t.Fatal(err) + } + if result.Phase != "applied" || !f.group.RestrictedToWorkflows || !reflect.DeepEqual(f.repoIDs, []int64{17}) || !reflect.DeepEqual(f.group.SelectedWorkflows, []string{runnerPolicyFixture().Workflow}) || f.variable == nil || f.variable.Value != runnerPolicyFixture().Variable.Value { + t.Fatalf("wrong exact readback: %+v", result) + } + writes := len(f.writes) + r = policyReconcilerFixture(t, f, dir) + if _, err := r.Apply(context.Background(), "transaction-1"); err != nil { + t.Fatal(err) + } + if len(f.writes) != writes { + t.Fatal("identical retry repeated writes") + } + before, err := os.ReadFile(filepath.Join(dir, "runner-policy-audit.jsonl")) + if err != nil { + t.Fatal(err) + } + result, err = r.Rollback(context.Background(), "transaction-1") + if err != nil { + t.Fatal(err) + } + if result.Phase != "rolled-back-restrictive" || f.variable != nil || !f.group.RestrictedToWorkflows || !reflect.DeepEqual(f.repoIDs, []int64{17}) { + t.Fatal("rollback broadened access or failed variable restore") + } + after, err := os.ReadFile(filepath.Join(dir, "runner-policy-audit.jsonl")) + if err != nil { + t.Fatal(err) + } + if !bytes.HasPrefix(after, before) || bytes.Contains(after, []byte("known-private-value")) { + t.Fatal("audit overwritten or credential disclosed") + } + for _, write := range f.writes { + if strings.Contains(write, "/runners") || strings.Contains(write, "/runner-groups/1") || strings.Contains(write, "registration-token") { + t.Fatalf("protected state mutated: %s", write) + } + } + }) + } +} + +func TestRunnerPolicyRetainedReadbackPaginationAndJournalDenial(t *testing.T) { + for _, mode := range []string{"retained", "pagination-origin", "readback", "audit-unwritable", "missing-list", "missing-safety", "variable-failure"} { + t.Run(mode, func(t *testing.T) { + f := newPolicyAPIFake(t) + dir := t.TempDir() + if mode == "retained" { + f.retained = true + } + if mode == "pagination-origin" { + f.crossOrigin = "https://other.example.test/?known-private-value" + } + if mode == "readback" { + f.badReadback = true + } + if mode == "missing-list" { + f.missingList = true + } + if mode == "missing-safety" { + f.missingSafety = true + } + if mode == "variable-failure" { + f.variableFailure = true + } + if mode == "audit-unwritable" { + if err := os.Mkdir(filepath.Join(dir, "runner-policy-audit.jsonl"), 0700); err != nil { + t.Fatal(err) + } + } + r := policyReconcilerFixture(t, f, dir) + _, err := r.Plan(context.Background(), "transaction-1", runnerPolicyFixture()) + if mode == "readback" && err == nil { + _, err = r.Apply(context.Background(), "transaction-1") + } + if err == nil || strings.Contains(err.Error(), "known-private-value") { + t.Fatalf("unsafe policy operation accepted/disclosed: %v", err) + } + if mode != "readback" && len(f.writes) != 0 { + t.Fatal("denial wrote GitHub state") + } + }) + } +} + +func TestRunnerPolicyInterruptedIntentResumesWithoutRepeatingConfirmedWrite(t *testing.T) { + for _, step := range []string{"PATCH /orgs/GoCodeAlone/actions/runner-groups/2", "PUT /orgs/GoCodeAlone/actions/runner-groups/2/repositories", "POST /repos/GoCodeAlone/workflow-compute/actions/variables"} { + for _, beforeWrite := range []bool{true, false} { + t.Run(fmt.Sprintf("%s/before=%t", step, beforeWrite), func(t *testing.T) { + f := newPolicyAPIFake(t) + f.interruptWrite, f.beforeWrite = step, beforeWrite + dir := t.TempDir() + r := policyReconcilerFixture(t, f, dir) + if _, err := r.Plan(context.Background(), "interrupted", runnerPolicyFixture()); err != nil { + t.Fatal(err) + } + if _, err := r.Apply(context.Background(), "interrupted"); err == nil { + t.Fatal("unconfirmed write incorrectly completed") + } + root, err := r.openAudit() + if err != nil { + t.Fatal(err) + } + _, last, err := r.load(root, "interrupted") + _ = root.Close() + if err != nil || last.Phase != "write-intent" { + t.Fatalf("interrupted transaction lacks durable intent: %+v %v", last, err) + } + f.mu.Lock() + f.blockReads, f.interruptWrite = false, "" + f.mu.Unlock() + r = policyReconcilerFixture(t, f, dir) + result, err := r.Apply(context.Background(), "interrupted") + if err != nil || result.Phase != "applied" { + t.Fatalf("new reconciler failed to resume exact intent: %+v %v", result, err) + } + f.mu.Lock() + defer f.mu.Unlock() + count := 0 + for _, write := range f.writes { + if write == step { + count++ + } + } + want := 1 + if beforeWrite { + want = 2 + } + if count != want || f.variable == nil || f.variable.Value != runnerPolicyFixture().Variable.Value { + t.Fatalf("resume repeated confirmed mutation or missed desired value: writes=%d want=%d", count, want) + } + }) + } + } +} + +func TestRunnerPolicyRollbackRestoresExistingVariableWithoutBroadening(t *testing.T) { + f := newPolicyAPIFake(t) + desired := runnerPolicyFixture() + f.repoIDs = []int64{17} + f.group.RestrictedToWorkflows = true + f.group.SelectedWorkflows = []string{desired.Workflow} + original := RunnerPolicyVariable{Name: desired.Variable.Name, Value: "https://previous.example.test"} + f.variable = &original + dir := t.TempDir() + r := policyReconcilerFixture(t, f, dir) + if _, err := r.Plan(context.Background(), "restore", desired); err != nil { + t.Fatal(err) + } + if _, err := r.Apply(context.Background(), "restore"); err != nil { + t.Fatal(err) + } + r = policyReconcilerFixture(t, f, dir) + result, err := r.Rollback(context.Background(), "restore") + if err != nil || result.Phase != "rolled-back" { + t.Fatalf("exact safe rollback failed: %+v %v", result, err) + } + f.mu.Lock() + defer f.mu.Unlock() + if f.variable == nil || *f.variable != original || !reflect.DeepEqual(f.repoIDs, []int64{17}) || !reflect.DeepEqual(f.group.SelectedWorkflows, []string{desired.Workflow}) { + t.Fatal("rollback failed exact variable or safe ACL readback") + } + if len(f.writes) != 2 || f.writes[0] != "PATCH /repos/GoCodeAlone/workflow-compute/actions/variables/WORKFLOW_COMPUTE_STAGING_URL" || f.writes[1] != f.writes[0] { + t.Fatalf("safe rollback performed unrelated mutations: %v", f.writes) + } +} diff --git a/plugin.contracts.json b/plugin.contracts.json index b956f66..fa70034 100644 --- a/plugin.contracts.json +++ b/plugin.contracts.json @@ -1,5 +1,6 @@ { "version": "1", + "protocolTypes": [{"name": "GitHubRunnerPolicy", "wire": "json", "schemaRef": "schemas/github-runner-policy.schema.json", "protocolVersion": "github-runner-policy.v1", "commands": ["github runner-policy reconcile plan", "github runner-policy reconcile apply", "github runner-policy reconcile rollback"]}], "contracts": [ { "kind": "module", diff --git a/plugin.json b/plugin.json index 4b181d1..791919c 100644 --- a/plugin.json +++ b/plugin.json @@ -6,7 +6,7 @@ "license": "MIT", "type": "external", "tier": "community", - "minEngineVersion": "0.61.0", + "minEngineVersion": "0.86.0", "required_secrets": [ { "name": "GITHUB_APP_PRIVATE_KEY", @@ -22,27 +22,27 @@ { "os": "linux", "arch": "amd64", - "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v1.0.1/workflow-plugin-github-linux-amd64.tar.gz" + "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v0.0.0/github-linux-amd64.tar.gz" }, { "os": "linux", "arch": "arm64", - "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v1.0.1/workflow-plugin-github-linux-arm64.tar.gz" + "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v0.0.0/github-linux-arm64.tar.gz" }, { "os": "darwin", "arch": "amd64", - "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v1.0.1/workflow-plugin-github-darwin-amd64.tar.gz" + "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v0.0.0/github-darwin-amd64.tar.gz" }, { "os": "darwin", "arch": "arm64", - "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v1.0.1/workflow-plugin-github-darwin-arm64.tar.gz" + "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v0.0.0/github-darwin-arm64.tar.gz" }, { "os": "windows", "arch": "amd64", - "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v1.0.1/workflow-plugin-github-windows-amd64.tar.gz" + "url": "https://github.com/GoCodeAlone/workflow-plugin-github/releases/download/v0.0.0/github-windows-amd64.tar.gz" } ], "moduleTypes": ["git.webhook", "github.app", "github.runner_provider"], @@ -66,6 +66,7 @@ ], "triggerTypes": [], "capabilities": { + "cliCommands": [{"name": "github", "description": "Audited GitHub runner policy reconciliation", "flagsPassthrough": true}], "configProvider": false, "moduleTypes": ["git.webhook", "github.app", "github.runner_provider"], "stepTypes": [ diff --git a/provider_input_schema.go b/provider_input_schema.go index e944819..a4ac6d6 100644 --- a/provider_input_schema.go +++ b/provider_input_schema.go @@ -3,6 +3,7 @@ package githubplugin import ( _ "embed" "encoding/json" + "errors" "fmt" "sync" @@ -23,6 +24,9 @@ var githubRunnerConfigSchemaDocument []byte //go:embed schemas/github-runner-ephemeral-job-output.schema.json var githubRunnerOutputSchemaDocument []byte +//go:embed schemas/github-runner-policy.schema.json +var githubRunnerPolicySchemaDocument []byte + var ( githubRunnerInputSchemaOnce sync.Once githubRunnerInputSchema *jsonschema.Schema @@ -33,8 +37,26 @@ var ( githubRunnerOutputSchemaOnce sync.Once githubRunnerOutputSchema *jsonschema.Schema githubRunnerOutputSchemaErr error + githubRunnerPolicySchemaOnce sync.Once + githubRunnerPolicySchema *jsonschema.Schema + githubRunnerPolicySchemaErr error ) +// ValidateRunnerPolicyDocument validates the CLI JSON document, not a runtime grant. +func ValidateRunnerPolicyDocument(data json.RawMessage) error { + githubRunnerPolicySchemaOnce.Do(func() { + githubRunnerPolicySchema, githubRunnerPolicySchemaErr = compileEmbeddedSchema("schema://github/runner-policy/v1", githubRunnerPolicySchemaDocument) + }) + if githubRunnerPolicySchemaErr != nil { + return errors.New("embedded runner policy schema could not be compiled") + } + var value any + if json.Unmarshal(data, &value) != nil || githubRunnerPolicySchema.Validate(value) != nil { + return errors.New("runner policy does not match its strict schema") + } + return nil +} + func ValidateGitHubRunnerProviderConfig(config providercontract.Config) error { return ValidateGitHubRunnerProviderConfigValue(config) } diff --git a/release_packaging_test.go b/release_packaging_test.go index c3a0558..cebaabb 100644 --- a/release_packaging_test.go +++ b/release_packaging_test.go @@ -1,18 +1,187 @@ package githubplugin_test import ( + "archive/tar" "bytes" + "compress/gzip" + "context" "encoding/json" "fmt" + "io" "os" + "os/exec" "path/filepath" "strings" "testing" + "time" "github.com/GoCodeAlone/workflow-plugin-github/internal/retainedprovider" + engineplugin "github.com/GoCodeAlone/workflow/plugin" "github.com/santhosh-tekuri/jsonschema/v6" + "gopkg.in/yaml.v3" ) +func TestGitHubCLIReleaseArchiveMatrix(t *testing.T) { + base := os.Getenv("GITHUB_PACKAGING_PROOF_DIR") + if base == "" { + t.Skip("set GITHUB_PACKAGING_PROOF_DIR for the full GoReleaser archive proof") + } + if err := os.MkdirAll(base, 0700); err != nil { + t.Fatal(err) + } + dir, err := os.MkdirTemp(base, "matrix-") + if err != nil { + t.Fatal(err) + } + renderDir, err := os.MkdirTemp(".", ".goreleaser-proof-") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.RemoveAll(renderDir) }) + source, err := os.ReadFile(".goreleaser.yaml") + if err != nil { + t.Fatal(err) + } + var config yaml.Node + if err := yaml.Unmarshal(source, &config); err != nil { + t.Fatal(err) + } + config.Content[0].Content = append(config.Content[0].Content, &yaml.Node{Kind: yaml.ScalarNode, Value: "dist"}, &yaml.Node{Kind: yaml.ScalarNode, Value: filepath.Join(dir, "dist")}) + data, err := yaml.Marshal(&config) + if err != nil { + t.Fatal(err) + } + configPath := filepath.Join(dir, "goreleaser.yaml") + if err := os.WriteFile(configPath, data, 0600); err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute) + defer cancel() + cmd := exec.CommandContext(ctx, "goreleaser", "release", "--snapshot", "--clean", "--parallelism", "2", "--config", configPath) + cmd.Env = append(os.Environ(), "GORELEASER_RENDER_DIR="+filepath.Clean(renderDir)) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("actual GoReleaser archive matrix failed: %v\n%s", err, out) + } + for _, platform := range []string{"linux-amd64", "linux-arm64", "darwin-amd64", "darwin-arm64", "windows-amd64", "windows-arm64"} { + path := filepath.Join(dir, "dist", "github-"+platform+".tar.gz") + file, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + reader, err := gzip.NewReader(file) + if err != nil { + _ = file.Close() + t.Fatal(err) + } + tarReader := tar.NewReader(reader) + binaries := 0 + manifestFound := false + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + if header.Name == "github" || header.Name == "github.exe" { + binaries++ + } else if header.Mode&0111 != 0 && header.Typeflag == tar.TypeReg { + t.Fatalf("unexpected executable in %s: %s", platform, header.Name) + } + if header.Name == "plugin.json" { + var manifest struct { + Version string `json:"version"` + Downloads []struct { + URL string `json:"url"` + } `json:"downloads"` + } + if err := json.NewDecoder(tarReader).Decode(&manifest); err != nil { + t.Fatal(err) + } + if manifest.Version == "0.0.0" || manifest.Version == "" { + t.Fatal("archive manifest version was not rendered") + } + if _, err := engineplugin.ParseSemver(manifest.Version); err != nil { + t.Fatalf("archive manifest version is not accepted by the released Workflow host: %v", err) + } + for _, download := range manifest.Downloads { + if !strings.Contains(download.URL, "/download/v"+manifest.Version+"/github-") { + t.Fatal("archive download URL did not bind the rendered version and dedicated CLI asset") + } + } + manifestFound = true + } + } + if err := reader.Close(); err != nil { + t.Fatal(err) + } + if err := file.Close(); err != nil { + t.Fatal(err) + } + if binaries != 1 || !manifestFound { + t.Fatalf("archive %s does not have one entrypoint and normalized manifest", platform) + } + t.Logf("archive=%s", path) + } + t.Logf("proof_dir=%s", dir) +} + +func TestGitHubCLIReleaseIsSingleEntrypoint(t *testing.T) { + data, err := os.ReadFile(".goreleaser.yaml") + if err != nil { + t.Fatal(err) + } + var release struct { + Builds []struct{ ID, Main, Binary string } + Archives []struct { + ID string + IDs []string `yaml:"ids"` + Files []any + } + } + if err := yaml.Unmarshal(data, &release); err != nil { + t.Fatal(err) + } + foundBuild, foundArchive := false, false + for _, build := range release.Builds { + if build.ID == "github-cli" { + foundBuild = build.Main == "./cmd/workflow-plugin-github" && build.Binary == "github" + } + } + for _, archive := range release.Archives { + if archive.ID == "github-cli" { + foundArchive = len(archive.IDs) == 1 && archive.IDs[0] == "github-cli" + } + } + if !foundBuild || !foundArchive { + t.Fatal("release must have a dedicated single-entry github CLI archive") + } + data, err = os.ReadFile("plugin.json") + if err != nil { + t.Fatal(err) + } + var manifest struct { + Capabilities struct { + CLICommands []struct{ Name string } `json:"cliCommands"` + } `json:"capabilities"` + } + if err := json.Unmarshal(data, &manifest); err != nil { + t.Fatal(err) + } + if len(manifest.Capabilities.CLICommands) != 1 || manifest.Capabilities.CLICommands[0].Name != "github" { + t.Fatal("manifest must contribute only top-level github") + } + data, err = os.ReadFile("cmd/workflow-plugin-github/main.go") + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(data, []byte("sdk.ServePluginFull(")) { + t.Fatal("entrypoint must use sdk.ServePluginFull") + } +} + func TestReleaseArchiveIncludesGitHubRunnerProvider(t *testing.T) { data, err := os.ReadFile(".goreleaser.yaml") if err != nil { diff --git a/schemas/github-runner-policy.schema.json b/schemas/github-runner-policy.schema.json new file mode 100644 index 0000000..79a7b62 --- /dev/null +++ b/schemas/github-runner-policy.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema://github/runner-policy/v1", + "title": "GitHub Runner Policy", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "organization", "runner_group", "repository", "workflow", "visibility", "allows_public_repositories", "restricted_to_workflows", "runner_ids", "protected_runner_ids", "variable"], + "properties": { + "schema_version": {"const": "github-runner-policy.v1"}, + "organization": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$"}, + "runner_group": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$", "not": {"pattern": "^[Dd][Ee][Ff][Aa][Uu][Ll][Tt]$"}}, + "repository": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}/[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$"}, + "workflow": {"type": "string", "minLength": 1, "maxLength": 512}, + "visibility": {"const": "selected"}, + "allows_public_repositories": {"const": false}, + "restricted_to_workflows": {"const": true}, + "runner_ids": {"type": "array", "maxItems": 0}, + "protected_runner_ids": {"type": "array", "maxItems": 100, "uniqueItems": true, "items": {"type": "integer", "minimum": 1}}, + "variable": { + "type": "object", "additionalProperties": false, "required": ["name", "value"], + "properties": { + "name": {"type": "string", "pattern": "^[A-Z_][A-Z0-9_]{0,99}$"}, + "value": {"type": "string", "maxLength": 2048, "pattern": "^https://[^\\s?#@]+$"} + } + } + } +}