diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1966964..13013e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: go-version: 1.22.4 - name: Import GPG Key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6.1.0 + uses: crazy-max/ghaction-import-gpg@v6.2.0 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39c50f2..28189fb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: - run: go mod download - run: go build -v . - name: Run linters - uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 + uses: golangci/golangci-lint-action@2e788936b09dd82dc280e845628a40d2ba6b204c # v6.3.1 with: version: latest @@ -38,6 +38,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 + with: + terraform_version: "1.9.*" + terraform_wrapper: false - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: "go.mod" @@ -70,7 +74,7 @@ jobs: with: go-version-file: "go.mod" cache: true - - uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1 + - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 with: terraform_version: ${{ matrix.terraform }} terraform_wrapper: false diff --git a/.golangci.yml b/.golangci.yml index 223cf95..29ddfe8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,19 +9,18 @@ linters: enable: - durationcheck - errcheck - - exportloopref - forcetypeassert - godot - gofmt - gosimple + - govet - ineffassign - makezero - misspell - nilerr - predeclared - staticcheck - - tenv - unconvert - unparam - unused - - vet \ No newline at end of file + - usetesting diff --git a/README.md b/README.md index 611d9bd..92434e5 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ If it is found that building a particular dev container would produce the same i Take a look at the [`envbuilder_cached_image_resource.tf`](./examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf) example for a detailed usage example. For use with [Coder](https://github.com/coder/coder), see the [Dev Containers documentation](https://coder.com/docs/templates/dev-containers) and check out the example templates: -- [Docker](https://github.com/coder/coder/tree/main/examples/templates/devcontainer-docker) -- [Kubernetes](https://github.com/coder/coder/tree/main/examples/templates/devcontainer-kuberntes) -- [AWS VM](https://github.com/coder/coder/tree/main/examples/templates/devcontainer-aws-vm) -- [GCP VM](https://github.com/coder/coder/tree/main/examples/templates/devcontainer-gcp-vm) +- [Docker](https://github.com/coder/coder/tree/main/examples/templates/docker-devcontainer) +- [Kubernetes](https://github.com/coder/coder/tree/main/examples/templates/kubernetes-devcontainer) +- [AWS VM](https://github.com/coder/coder/tree/main/examples/templates/aws-devcontainer) +- [GCP VM](https://github.com/coder/coder/tree/main/examples/templates/gcp-devcontainer) ## Requirements diff --git a/docs/index.md b/docs/index.md index 8148773..8bedaaa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,12 +3,14 @@ page_title: "envbuilder Provider" subcategory: "" description: |- - + The Envbuilder provider can be used to check for the presence of a container image previously built by Envbuilder https://github.com/coder/envbuilder. + This allows re-using a previously built image pushed to a container registry without having to rebuild it. --- # envbuilder Provider - +The Envbuilder provider can be used to check for the presence of a container image previously built by [Envbuilder](https://github.com/coder/envbuilder). +This allows re-using a previously built image pushed to a container registry without having to rebuild it. ## Example Usage diff --git a/docs/resources/cached_image.md b/docs/resources/cached_image.md index 8498720..842d806 100644 --- a/docs/resources/cached_image.md +++ b/docs/resources/cached_image.md @@ -25,6 +25,7 @@ The cached image resource can be used to retrieve a cached image produced by env - `base_image_cache_dir` (String) (Envbuilder option) The path to a directory where the base image can be found. This should be a read-only directory solely mounted for the purpose of caching the base image. - `build_context_path` (String) (Envbuilder option) Can be specified when a DockerfilePath is specified outside the base WorkspaceFolder. This path MUST be relative to the WorkspaceFolder path into which the repo is cloned. +- `build_secrets` (Map of String) The secrets to use for the build. This is a map of key-value pairs. - `cache_ttl_days` (Number) (Envbuilder option) The number of days to use cached layers before expiring them. Defaults to 7 days. - `devcontainer_dir` (String) (Envbuilder option) The path to the folder containing the devcontainer.json file that will be used to build the workspace and can either be an absolute path or a path relative to the workspace folder. If not provided, defaults to `.devcontainer`. - `devcontainer_json_path` (String) (Envbuilder option) The path to a devcontainer.json file that is either an absolute path or a path relative to DevcontainerDir. This can be used in cases where one wants to substitute an edited devcontainer.json file for the one that exists in the repo. @@ -37,6 +38,7 @@ The cached image resource can be used to retrieve a cached image produced by env - `git_clone_single_branch` (Boolean) (Envbuilder option) Clone only a single branch of the Git repository. - `git_http_proxy_url` (String) (Envbuilder option) The URL for the HTTP proxy. This is optional. - `git_password` (String, Sensitive) (Envbuilder option) The password to use for Git authentication. This is optional. +- `git_ssh_private_key_base64` (String, Sensitive) (Envbuilder option) Base64 encoded SSH private key to be used for Git authentication. - `git_ssh_private_key_path` (String) (Envbuilder option) Path to an SSH private key to be used for Git authentication. - `git_username` (String) (Envbuilder option) The username to use for Git authentication. This is optional. - `ignore_paths` (List of String) (Envbuilder option) The comma separated list of paths to ignore when building the workspace. @@ -48,7 +50,8 @@ The cached image resource can be used to retrieve a cached image produced by env ### Read-Only -- `env` (List of String, Sensitive) Computed envbuilder configuration to be set for the container. May contain secrets. +- `env` (List of String, Sensitive) Computed envbuilder configuration to be set for the container in the form of a list of strings of `key=value`. May contain secrets. +- `env_map` (Map of String, Sensitive) Computed envbuilder configuration to be set for the container in the form of a key-value map. May contain secrets. - `exists` (Boolean) Whether the cached image was exists or not for the given config. - `id` (String) Cached image identifier. This will generally be the image's SHA256 digest. - `image` (String) Outputs the cached image repo@digest if it exists, and builder image otherwise. diff --git a/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf b/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf index 304aea4..af90219 100644 --- a/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf +++ b/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf @@ -66,10 +66,11 @@ resource "envbuilder_cached_image" "example" { builder_image = var.builder_image git_url = var.repo_url cache_repo = local.cache_repo + insecure = true extra_env = { "ENVBUILDER_VERBOSE" : "true" "ENVBUILDER_INSECURE" : "true" # due to local registry - "ENVBUILDER_INIT_SCRIPT" : "sleep infinity" + "ENVBUILDER_INIT_SCRIPT" : "#!/usr/bin/env bash\necho Hello && sleep infinity" "ENVBUILDER_PUSH_IMAGE" : "true" } depends_on = [docker_container.registry] @@ -77,8 +78,8 @@ resource "envbuilder_cached_image" "example" { // Run the cached image. Depending on the contents of // the cache repo, this will either be var.builder_image -// or a previously built image pusehd to var.cache_repo. -// Running `terraform apply` once (assuming empty cache) +// or a previously built image pushed to var.cache_repo. +// Running `terraform apply` once (assuming empty cache) // will result in the builder image running, and the built // image being pushed to the cache repo. // Running `terraform apply` again will result in the @@ -105,4 +106,3 @@ output "id" { output "image" { value = envbuilder_cached_image.example.image } - diff --git a/go.mod b/go.mod index 66e2fb6..ad8271e 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,32 @@ module github.com/coder/terraform-provider-envbuilder -go 1.22.4 +go 1.23.0 + +toolchain go1.23.7 // We use our own Kaniko fork. -replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240807142221-ffc5e60fca41 +replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20241120132148-131d6094d781 // Required to import codersdk due to gvisor dependency. replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20240702054557-aa558fbe5374 require ( github.com/GoogleContainerTools/kaniko v1.9.2 - github.com/coder/envbuilder v1.0.0-rc.0.0.20240807151028-6e5bfa5faa29 - github.com/docker/docker v26.1.4+incompatible - github.com/gliderlabs/ssh v0.3.7 - github.com/go-git/go-billy/v5 v5.5.0 - github.com/go-git/go-git/v5 v5.12.0 + github.com/coder/envbuilder v1.1.0 + github.com/coder/serpent v0.8.0 + github.com/docker/docker v27.3.1+incompatible + github.com/gliderlabs/ssh v0.3.8 + github.com/go-git/go-billy/v5 v5.6.1 + github.com/go-git/go-git/v5 v5.13.1 github.com/google/go-containerregistry v0.20.2 github.com/google/uuid v1.6.0 - github.com/hashicorp/terraform-plugin-docs v0.19.4 + github.com/hashicorp/terraform-plugin-docs v0.21.0 github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 - github.com/stretchr/testify v1.9.0 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.10.0 ) require ( @@ -55,9 +59,9 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect - github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.11.7 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect @@ -84,41 +88,40 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect - github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chainguard-dev/git-urls v1.0.2 // indirect github.com/charmbracelet/lipgloss v0.8.0 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect - github.com/cilium/ebpf v0.12.3 // indirect - github.com/cloudflare/circl v1.3.7 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/coder/coder/v2 v2.10.1-0.20240704130443-c2d44d16a352 // indirect github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 // indirect github.com/coder/quartz v0.1.0 // indirect github.com/coder/retry v1.5.1 // indirect - github.com/coder/serpent v0.7.0 // indirect github.com/coder/terraform-provider-coder v0.23.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect - github.com/containerd/cgroups/v3 v3.0.2 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.21 // indirect + github.com/containerd/containerd/api v1.7.19 // indirect github.com/containerd/continuity v0.4.3 // indirect + github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect - github.com/containerd/ttrpc v1.2.3 // indirect - github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/containerd/ttrpc v1.2.5 // indirect + github.com/containerd/typeurl/v2 v2.2.0 // indirect github.com/coreos/go-iptables v0.6.0 // indirect github.com/coreos/go-oidc/v3 v3.10.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/distribution/distribution/v3 v3.0.0-alpha.1 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v27.1.1+incompatible // indirect + github.com/docker/cli v27.2.1+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.1 // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -132,23 +135,23 @@ require ( github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/nftables v0.2.0 // indirect - github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect - github.com/hashicorp/cli v1.1.6 // indirect + github.com/hashicorp/cli v1.1.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -163,12 +166,12 @@ require ( github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect - github.com/hashicorp/hc-install v0.8.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hc-install v0.9.1 // indirect github.com/hashicorp/hcl/v2 v2.21.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.21.0 // indirect - github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-exec v0.22.0 // indirect + github.com/hashicorp/terraform-json v0.24.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect @@ -182,13 +185,12 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect github.com/jsimonetti/rtnetlink v1.3.5 // indirect - github.com/karrick/godirwalk v1.16.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect @@ -204,17 +206,18 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.13.1 // indirect + github.com/moby/buildkit v0.16.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/swarmkit/v2 v2.0.0-20230315203717-e28e8ba9bc83 // indirect github.com/moby/sys/mount v0.3.3 // indirect - github.com/moby/sys/mountinfo v0.7.1 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/signal v0.7.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/symlink v0.2.0 // indirect - github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/reflow v0.3.0 // indirect @@ -222,7 +225,7 @@ require ( github.com/oklog/run v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runtime-spec v1.1.0 // indirect + github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/otiai10/copy v1.14.0 // indirect github.com/outcaste-io/ristretto v0.2.3 // indirect @@ -237,7 +240,7 @@ require ( github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.15.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.1.0 // indirect @@ -248,11 +251,10 @@ require ( github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.2 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d // indirect github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect @@ -261,6 +263,8 @@ require ( github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 // indirect github.com/tcnksm/go-httpstat v0.2.0 // indirect github.com/tinylib/msgp v1.1.8 // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect + github.com/twpayne/go-vfs/v5 v5.0.4 // indirect github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect github.com/valyala/fasthttp v1.55.0 // indirect github.com/vbatts/tar-split v0.11.5 // indirect @@ -271,9 +275,9 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/yuin/goldmark v1.7.1 // indirect + github.com/yuin/goldmark v1.7.7 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect - github.com/zclconf/go-cty v1.15.0 // indirect + github.com/zclconf/go-cty v1.16.2 // indirect github.com/zeebo/errs v1.3.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect go.etcd.io/etcd/raft/v3 v3.5.6 // indirect @@ -290,17 +294,17 @@ require ( go.uber.org/atomic v1.11.0 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.36.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.23.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect diff --git a/go.sum b/go.sum index 4215a54..90d9f70 100644 --- a/go.sum +++ b/go.sum @@ -77,12 +77,12 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= -github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= +github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= @@ -144,8 +144,8 @@ 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/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= @@ -178,59 +178,61 @@ github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb2 github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/coder/coder/v2 v2.10.1-0.20240704130443-c2d44d16a352 h1:L/EjCuZxs5tOcqqCaASj/nu65TRYEFcTt8qRQfHZXX0= github.com/coder/coder/v2 v2.10.1-0.20240704130443-c2d44d16a352/go.mod h1:P1KoQSgnKEAG6Mnd3YlGzAophty+yKA9VV48LpfNRvo= -github.com/coder/envbuilder v1.0.0-rc.0.0.20240807151028-6e5bfa5faa29 h1:PhJBofIrh6NGuTQ93nW7/KWcYX6Ju0PGoE/BbNvf87o= -github.com/coder/envbuilder v1.0.0-rc.0.0.20240807151028-6e5bfa5faa29/go.mod h1:lm33s3+chqnl7lB4avNFfDH5gXDYSunYD7/y4bJ/LMA= -github.com/coder/kaniko v0.0.0-20240807142221-ffc5e60fca41 h1:1Ye7AcLnuT5IDv6il7Fxo+aqpzlWfedkpraCCwx8Lyo= -github.com/coder/kaniko v0.0.0-20240807142221-ffc5e60fca41/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM= +github.com/coder/envbuilder v1.1.0 h1:OcICg3FzwFHzBDw+60tW7dgCxfkTxt/C6faz/cAfwDE= +github.com/coder/envbuilder v1.1.0/go.mod h1:WgqCgSz6XzXSoTGMMIuBf+0D38iofTqAwQCIlEmohX0= +github.com/coder/kaniko v0.0.0-20241120132148-131d6094d781 h1:/4SMdrjLQL1BseLSnMd9nYQSI+E63CXcyFGC7ZHHj8I= +github.com/coder/kaniko v0.0.0-20241120132148-131d6094d781/go.mod h1:3rM/KOQ4LgF8mE+O1P6pLDa/E57mzxIxNdUOMKi1qpg= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= github.com/coder/quartz v0.1.0 h1:cLL+0g5l7xTf6ordRnUMMiZtRE8Sq5LxpghS63vEXrQ= github.com/coder/quartz v0.1.0/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= github.com/coder/retry v1.5.1/go.mod h1:blHMk9vs6LkoRT9ZHyuZo360cufXEhrxqvEzeMtRGoY= -github.com/coder/serpent v0.7.0 h1:zGpD2GlF3lKIVkMjNGKbkip88qzd5r/TRcc30X/SrT0= -github.com/coder/serpent v0.7.0/go.mod h1:REkJ5ZFHQUWFTPLExhXYZ1CaHFjxvGNRlLXLdsI08YA= +github.com/coder/serpent v0.8.0 h1:6OR+k6fekhSeEDmwwzBgnSjaa7FfGGrMlc3GoAEH9dg= +github.com/coder/serpent v0.8.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q= github.com/coder/tailscale v1.1.1-0.20240702054557-aa558fbe5374 h1:a5Eg7D5e2oAc0tN56ee4yxtiTo76ztpRlk6geljaZp8= github.com/coder/tailscale v1.1.1-0.20240702054557-aa558fbe5374/go.mod h1:rp6BIJxCp127/hvvDWNkHC9MxAlKvQfoOtBr8s5sCqo= github.com/coder/terraform-provider-coder v0.23.0 h1:DuNLWxhnGlXyG0g+OCAZRI6xd8+bJjIEnE4F3hYgA4E= github.com/coder/terraform-provider-coder v0.23.0/go.mod h1:wMun9UZ9HT2CzF6qPPBup1odzBpVUc0/xSFoXgdI3tk= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= -github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.21 h1:USGXRK1eOC/SX0L195YgxTHb0a00anxajOzgfN0qrCA= +github.com/containerd/containerd v1.7.21/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= +github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= +github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containerd/ttrpc v1.2.3 h1:4jlhbXIGvijRtNC8F/5CpuJZ7yKOBFGFOOXg1bkISz0= -github.com/containerd/ttrpc v1.2.3/go.mod h1:ieWsXucbb8Mj9PH0rXCw1i8IunRbbAiDkpXkbfflWBM= -github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= -github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= +github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= +github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -246,14 +248,14 @@ github.com/distribution/distribution/v3 v3.0.0-alpha.1 h1:jn7I1gvjOvmLztH1+1cLiU github.com/distribution/distribution/v3 v3.0.0-alpha.1/go.mod h1:LCp4JZp1ZalYg0W/TN05jarCQu+h4w7xc7ZfQF4Y/cY= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= -github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.2.1+incompatible h1:U5BPtiD0viUzjGAjV1p0MGB8eVA3L3cbIrnyWmSJI70= +github.com/docker/cli v27.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= -github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= @@ -269,8 +271,8 @@ github.com/ePirat/docker-credential-gitlabci v1.0.0 h1:YRkUSvkON6rT88vtscClAmPEY github.com/ePirat/docker-credential-gitlabci v1.0.0/go.mod h1:Ptmh+D0lzBQtgb6+QHjXl9HqOn3T1P8fKUHldiSQQGA= github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= +github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -297,20 +299,20 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= -github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= -github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= -github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 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= @@ -354,8 +356,9 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= @@ -402,8 +405,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8= github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -417,8 +420,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= -github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8= -github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4= +github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU= +github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU= 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= @@ -454,20 +457,20 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= -github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= -github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI= -github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ= +github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0= github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= -github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= -github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= -github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= -github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= +github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64= +github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ= +github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= +github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= +github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbKP/nteVNBaI3hW7iQ8= +github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s= github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE= github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= @@ -518,8 +521,6 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -550,11 +551,10 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -589,8 +589,8 @@ github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1 github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.13.1 h1:L8afOFhPq2RPJJSr/VyzbufwID7jquZVB7oFHbPRcPE= -github.com/moby/buildkit v0.13.1/go.mod h1:aNmNQKLBFYAOFuzQjR3VA27/FijlvtBD1pjNwTSN37k= +github.com/moby/buildkit v0.16.0 h1:wOVBj1o5YNVad/txPQNXUXdelm7Hs/i0PUFjzbK0VKE= +github.com/moby/buildkit v0.16.0/go.mod h1:Xqx/5GlrqE1yIRORk0NSCVDFpQAU1WjlT6KHYZdisIQ= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -602,16 +602,18 @@ github.com/moby/swarmkit/v2 v2.0.0-20230315203717-e28e8ba9bc83/go.mod h1:GvjR7mC github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= -github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= -github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= +github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= github.com/moby/sys/symlink v0.2.0 h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -628,16 +630,18 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= -github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= @@ -688,8 +692,8 @@ github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5E 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.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek= -github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -705,8 +709,8 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -718,8 +722,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= -github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -746,8 +750,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d h1:K3j02b5j2Iw1xoggN9B2DIEkhWGheqFOeDkdJdBrJI8= @@ -766,8 +770,12 @@ github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ0 github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8= +github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/twpayne/go-vfs/v5 v5.0.4 h1:/ne3h+rW7f5YOyOFguz+3ztfUwzOLR0Vts3y0mMAitg= +github.com/twpayne/go-vfs/v5 v5.0.4/go.mod h1:zTPFJUbgsEMFNSWnWQlLq9wh4AN83edZzx3VXbxrS1w= github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og= github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= @@ -805,12 +813,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= -github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= +github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= -github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= +github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= @@ -876,11 +884,11 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -889,8 +897,8 @@ 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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -910,11 +918,11 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -924,8 +932,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ 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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -957,23 +965,22 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 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= @@ -983,8 +990,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -1000,8 +1007,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f 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.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= 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= diff --git a/internal/imgutil/imgutil.go b/internal/imgutil/imgutil.go new file mode 100644 index 0000000..5c04441 --- /dev/null +++ b/internal/imgutil/imgutil.go @@ -0,0 +1,106 @@ +package imgutil + +import ( + "archive/tar" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + eboptions "github.com/coder/envbuilder/options" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// GetRemoteImage fetches the image manifest of the image. +func GetRemoteImage(imgRef string) (v1.Image, error) { + ref, err := name.ParseReference(imgRef) + if err != nil { + return nil, fmt.Errorf("parse reference: %w", err) + } + + img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + if err != nil { + return nil, fmt.Errorf("check remote image: %w", err) + } + + return img, nil +} + +// ExtractEnvbuilderFromImage reads the image located at imgRef and extracts +// MagicBinaryLocation to destPath. +func ExtractEnvbuilderFromImage(ctx context.Context, imgRef, destPath string) error { + var o eboptions.Options + o.SetDefaults() + needle := strings.TrimPrefix(o.BinaryPath, "/") + img, err := GetRemoteImage(imgRef) + if err != nil { + return fmt.Errorf("check remote image: %w", err) + } + + layers, err := img.Layers() + if err != nil { + return fmt.Errorf("get image layers: %w", err) + } + + // Check the layers in reverse order. The last layers are more likely to + // include the binary. + for i := len(layers) - 1; i >= 0; i-- { + ul, err := layers[i].Uncompressed() + if err != nil { + return fmt.Errorf("get uncompressed layer: %w", err) + } + + tr := tar.NewReader(ul) + for { + th, err := tr.Next() + if err == io.EOF { + break + } + + if err != nil { + return fmt.Errorf("read tar header: %w", err) + } + + name := filepath.Clean(th.Name) + if th.Typeflag != tar.TypeReg { + tflog.Debug(ctx, "skip non-regular file", map[string]any{"name": name, "layer_idx": i + 1}) + continue + } + + if name != needle { + tflog.Debug(ctx, "skip file", map[string]any{"name": name, "layer_idx": i + 1}) + continue + } + + tflog.Debug(ctx, "found file", map[string]any{"name": name, "layer_idx": i + 1}) + if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil { + return fmt.Errorf("create parent directories: %w", err) + } + destF, err := os.Create(destPath) + if err != nil { + return fmt.Errorf("create dest file for writing: %w", err) + } + defer destF.Close() + _, err = io.Copy(destF, tr) + if err != nil { + return fmt.Errorf("copy dest file from image: %w", err) + } + if err := destF.Close(); err != nil { + return fmt.Errorf("close dest file: %w", err) + } + + if err := os.Chmod(destPath, 0o755); err != nil { + return fmt.Errorf("chmod file: %w", err) + } + return nil + } + } + + return fmt.Errorf("extract envbuilder binary from image %q: %w", imgRef, os.ErrNotExist) +} diff --git a/internal/provider/cached_image_resource.go b/internal/provider/cached_image_resource.go index e165453..986d628 100644 --- a/internal/provider/cached_image_resource.go +++ b/internal/provider/cached_image_resource.go @@ -1,10 +1,8 @@ package provider import ( - "archive/tar" "context" "fmt" - "io" "net/http" "os" "path/filepath" @@ -12,16 +10,14 @@ import ( kconfig "github.com/GoogleContainerTools/kaniko/pkg/config" "github.com/coder/envbuilder" - "github.com/coder/envbuilder/constants" - eblog "github.com/coder/envbuilder/log" eboptions "github.com/coder/envbuilder/options" + "github.com/coder/terraform-provider-envbuilder/internal/imgutil" + "github.com/coder/terraform-provider-envbuilder/internal/tfutil" "github.com/go-git/go-billy/v5/osfs" - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" @@ -30,6 +26,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" ) @@ -52,30 +49,33 @@ type CachedImageResourceModel struct { CacheRepo types.String `tfsdk:"cache_repo"` GitURL types.String `tfsdk:"git_url"` // Optional "inputs". - BaseImageCacheDir types.String `tfsdk:"base_image_cache_dir"` - BuildContextPath types.String `tfsdk:"build_context_path"` - CacheTTLDays types.Int64 `tfsdk:"cache_ttl_days"` - DevcontainerDir types.String `tfsdk:"devcontainer_dir"` - DevcontainerJSONPath types.String `tfsdk:"devcontainer_json_path"` - DockerfilePath types.String `tfsdk:"dockerfile_path"` - DockerConfigBase64 types.String `tfsdk:"docker_config_base64"` - ExitOnBuildFailure types.Bool `tfsdk:"exit_on_build_failure"` - ExtraEnv types.Map `tfsdk:"extra_env"` - FallbackImage types.String `tfsdk:"fallback_image"` - GitCloneDepth types.Int64 `tfsdk:"git_clone_depth"` - GitCloneSingleBranch types.Bool `tfsdk:"git_clone_single_branch"` - GitHTTPProxyURL types.String `tfsdk:"git_http_proxy_url"` - GitPassword types.String `tfsdk:"git_password"` - GitSSHPrivateKeyPath types.String `tfsdk:"git_ssh_private_key_path"` - GitUsername types.String `tfsdk:"git_username"` - IgnorePaths types.List `tfsdk:"ignore_paths"` - Insecure types.Bool `tfsdk:"insecure"` - RemoteRepoBuildMode types.Bool `tfsdk:"remote_repo_build_mode"` - SSLCertBase64 types.String `tfsdk:"ssl_cert_base64"` - Verbose types.Bool `tfsdk:"verbose"` - WorkspaceFolder types.String `tfsdk:"workspace_folder"` + BaseImageCacheDir types.String `tfsdk:"base_image_cache_dir"` + BuildContextPath types.String `tfsdk:"build_context_path"` + BuildSecrets types.Map `tfsdk:"build_secrets"` + CacheTTLDays types.Int64 `tfsdk:"cache_ttl_days"` + DevcontainerDir types.String `tfsdk:"devcontainer_dir"` + DevcontainerJSONPath types.String `tfsdk:"devcontainer_json_path"` + DockerfilePath types.String `tfsdk:"dockerfile_path"` + DockerConfigBase64 types.String `tfsdk:"docker_config_base64"` + ExitOnBuildFailure types.Bool `tfsdk:"exit_on_build_failure"` + ExtraEnv types.Map `tfsdk:"extra_env"` + FallbackImage types.String `tfsdk:"fallback_image"` + GitCloneDepth types.Int64 `tfsdk:"git_clone_depth"` + GitCloneSingleBranch types.Bool `tfsdk:"git_clone_single_branch"` + GitHTTPProxyURL types.String `tfsdk:"git_http_proxy_url"` + GitPassword types.String `tfsdk:"git_password"` + GitSSHPrivateKeyPath types.String `tfsdk:"git_ssh_private_key_path"` + GitSSHPrivateKeyBase64 types.String `tfsdk:"git_ssh_private_key_base64"` + GitUsername types.String `tfsdk:"git_username"` + IgnorePaths types.List `tfsdk:"ignore_paths"` + Insecure types.Bool `tfsdk:"insecure"` + RemoteRepoBuildMode types.Bool `tfsdk:"remote_repo_build_mode"` + SSLCertBase64 types.String `tfsdk:"ssl_cert_base64"` + Verbose types.Bool `tfsdk:"verbose"` + WorkspaceFolder types.String `tfsdk:"workspace_folder"` // Computed "outputs". Env types.List `tfsdk:"env"` + EnvMap types.Map `tfsdk:"env_map"` Exists types.Bool `tfsdk:"exists"` ID types.String `tfsdk:"id"` Image types.String `tfsdk:"image"` @@ -122,6 +122,11 @@ func (r *CachedImageResource) Schema(ctx context.Context, req resource.SchemaReq MarkdownDescription: "(Envbuilder option) Can be specified when a DockerfilePath is specified outside the base WorkspaceFolder. This path MUST be relative to the WorkspaceFolder path into which the repo is cloned.", Optional: true, }, + "build_secrets": schema.MapAttribute{ + MarkdownDescription: "The secrets to use for the build. This is a map of key-value pairs.", + ElementType: types.StringType, + Optional: true, + }, "cache_ttl_days": schema.Int64Attribute{ MarkdownDescription: "(Envbuilder option) The number of days to use cached layers before expiring them. Defaults to 7 days.", Optional: true, @@ -155,7 +160,6 @@ func (r *CachedImageResource) Schema(ctx context.Context, req resource.SchemaReq MarkdownDescription: "(Envbuilder option) Terminates upon a build failure. This is handy when preferring the FALLBACK_IMAGE in cases where no devcontainer.json or image is provided. However, it ensures that the container stops if the build process encounters an error.", Optional: true, }, - // TODO(mafredri): Map vs List? Support both? "extra_env": schema.MapAttribute{ MarkdownDescription: "Extra environment variables to set for the container. This may include envbuilder options.", ElementType: types.StringType, @@ -189,6 +193,11 @@ func (r *CachedImageResource) Schema(ctx context.Context, req resource.SchemaReq MarkdownDescription: "(Envbuilder option) Path to an SSH private key to be used for Git authentication.", Optional: true, }, + "git_ssh_private_key_base64": schema.StringAttribute{ + MarkdownDescription: "(Envbuilder option) Base64 encoded SSH private key to be used for Git authentication.", + Optional: true, + Sensitive: true, + }, "git_username": schema.StringAttribute{ MarkdownDescription: "(Envbuilder option) The username to use for Git authentication. This is optional.", Optional: true, @@ -225,9 +234,8 @@ func (r *CachedImageResource) Schema(ctx context.Context, req resource.SchemaReq }, // Computed "outputs". - // TODO(mafredri): Map vs List? Support both? "env": schema.ListAttribute{ - MarkdownDescription: "Computed envbuilder configuration to be set for the container. May contain secrets.", + MarkdownDescription: "Computed envbuilder configuration to be set for the container in the form of a list of strings of `key=value`. May contain secrets.", ElementType: types.StringType, Computed: true, Sensitive: true, @@ -235,6 +243,15 @@ func (r *CachedImageResource) Schema(ctx context.Context, req resource.SchemaReq listplanmodifier.RequiresReplace(), }, }, + "env_map": schema.MapAttribute{ + MarkdownDescription: "Computed envbuilder configuration to be set for the container in the form of a key-value map. May contain secrets.", + ElementType: types.StringType, + Computed: true, + Sensitive: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.RequiresReplace(), + }, + }, "exists": schema.BoolAttribute{ MarkdownDescription: "Whether the cached image was exists or not for the given config.", Computed: true, @@ -280,6 +297,17 @@ func (r *CachedImageResource) Configure(ctx context.Context, req resource.Config r.client = client } +// setComputedEnv sets data.Env and data.EnvMap based on the values of the +// other fields in the model. +func (data *CachedImageResourceModel) setComputedEnv(ctx context.Context, env map[string]string) diag.Diagnostics { + var diag, ds diag.Diagnostics + data.EnvMap, ds = basetypes.NewMapValueFrom(ctx, types.StringType, env) + diag = append(diag, ds...) + data.Env, ds = basetypes.NewListValueFrom(ctx, types.StringType, tfutil.DockerEnv(env)) + diag = append(diag, ds...) + return diag +} + func (r *CachedImageResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data CachedImageResourceModel @@ -289,6 +317,16 @@ func (r *CachedImageResource) Read(ctx context.Context, req resource.ReadRequest return } + // Get the options from the data model. + opts, diags := optionsFromDataModel(data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Set the expected environment variables. + computedEnv := computeEnvFromOptions(opts, tfutil.TFMapToStringMap(data.ExtraEnv)) + resp.Diagnostics.Append(data.setComputedEnv(ctx, computedEnv)...) + // If the previous state is that Image == BuilderImage, then we previously did // not find the image. We will need to run another cache probe. if data.Image.Equal(data.BuilderImage) { @@ -302,7 +340,7 @@ func (r *CachedImageResource) Read(ctx context.Context, req resource.ReadRequest } // Check the remote registry for the image we previously found. - img, err := getRemoteImage(data.Image.ValueString()) + img, err := imgutil.GetRemoteImage(data.Image.ValueString()) if err != nil { if !strings.Contains(err.Error(), "MANIFEST_UNKNOWN") { // Explicitly not making this an error diag. @@ -336,29 +374,6 @@ func (r *CachedImageResource) Read(ctx context.Context, req resource.ReadRequest data.Image = types.StringValue(fmt.Sprintf("%s@%s", data.CacheRepo.ValueString(), digest)) data.Exists = types.BoolValue(true) - // Set the expected environment variables. - for key, elem := range data.ExtraEnv.Elements() { - data.Env = appendKnownEnvToList(data.Env, key, elem) - } - - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_CACHE_REPO", data.CacheRepo) - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_GIT_URL", data.GitURL) - if !data.CacheTTLDays.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_CACHE_TTL_DAYS", data.CacheTTLDays) - } - if !data.GitUsername.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_GIT_USERNAME", data.GitUsername) - } - if !data.GitPassword.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_GIT_PASSWORD", data.GitPassword) - } - // Default to remote build mode. - if data.RemoteRepoBuildMode.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", types.BoolValue(true)) - } else { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", data.RemoteRepoBuildMode) - } - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -372,7 +387,18 @@ func (r *CachedImageResource) Create(ctx context.Context, req resource.CreateReq return } - cachedImg, err := r.runCacheProbe(ctx, data) + // Get the options from the data model. + opts, diags := optionsFromDataModel(data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Set the expected environment variables. + computedEnv := computeEnvFromOptions(opts, tfutil.TFMapToStringMap(data.ExtraEnv)) + resp.Diagnostics.Append(data.setComputedEnv(ctx, computedEnv)...) + + cachedImg, err := runCacheProbe(ctx, data.BuilderImage.ValueString(), opts) data.ID = types.StringValue(uuid.Nil.String()) data.Exists = types.BoolValue(err == nil) if err != nil { @@ -394,29 +420,6 @@ func (r *CachedImageResource) Create(ctx context.Context, req resource.CreateReq data.Image = types.StringValue(fmt.Sprintf("%s@%s", data.CacheRepo.ValueString(), digest)) data.ID = types.StringValue(digest.String()) } - // Compute the env attribute from the config map. - // TODO(mafredri): Convert any other relevant attributes given via schema. - for key, elem := range data.ExtraEnv.Elements() { - data.Env = appendKnownEnvToList(data.Env, key, elem) - } - - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_CACHE_REPO", data.CacheRepo) - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_GIT_URL", data.GitURL) - if !data.CacheTTLDays.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_CACHE_TTL_DAYS", data.CacheTTLDays) - } - if !data.GitUsername.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_GIT_USERNAME", data.GitUsername) - } - if !data.GitPassword.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_GIT_PASSWORD", data.GitPassword) - } - // Default to remote build mode. - if data.RemoteRepoBuildMode.IsNull() { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", types.BoolValue(true)) - } else { - data.Env = appendKnownEnvToList(data.Env, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", data.RemoteRepoBuildMode) - } // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -451,7 +454,7 @@ func (r *CachedImageResource) Delete(ctx context.Context, req resource.DeleteReq // runCacheProbe performs a 'fake build' of the requested image and ensures that // all of the resulting layers of the image are present in the configured cache // repo. Otherwise, returns an error. -func (r *CachedImageResource) runCacheProbe(ctx context.Context, data CachedImageResourceModel) (v1.Image, error) { +func runCacheProbe(ctx context.Context, builderImage string, opts eboptions.Options) (v1.Image, error) { tmpDir, err := os.MkdirTemp(os.TempDir(), "envbuilder-provider-cached-image-data-source") if err != nil { return nil, fmt.Errorf("unable to create temp directory: %s", err.Error()) @@ -463,7 +466,7 @@ func (r *CachedImageResource) runCacheProbe(ctx context.Context, data CachedImag }() oldKanikoDir := kconfig.KanikoDir - tmpKanikoDir := filepath.Join(tmpDir, constants.MagicDir) + tmpKanikoDir := filepath.Join(tmpDir, ".envbuilder") // Normally you would set the KANIKO_DIR environment variable, but we are importing kaniko directly. kconfig.KanikoDir = tmpKanikoDir tflog.Info(ctx, "set kaniko dir to "+tmpKanikoDir) @@ -475,210 +478,52 @@ func (r *CachedImageResource) runCacheProbe(ctx context.Context, data CachedImag if err := os.MkdirAll(tmpKanikoDir, 0o755); err != nil { return nil, fmt.Errorf("failed to create kaniko dir: %w", err) } + // Use the temporary directory as our 'magic dir'. + opts.WorkingDirBase = tmpKanikoDir // In order to correctly reproduce the final layer of the cached image, we // need the envbuilder binary used to originally build the image! envbuilderPath := filepath.Join(tmpDir, "envbuilder") - if err := extractEnvbuilderFromImage(ctx, data.BuilderImage.ValueString(), envbuilderPath); err != nil { + if err := imgutil.ExtractEnvbuilderFromImage(ctx, builderImage, envbuilderPath); err != nil { tflog.Error(ctx, "failed to fetch envbuilder binary from builder image", map[string]any{"err": err}) return nil, fmt.Errorf("failed to fetch the envbuilder binary from the builder image: %s", err.Error()) } - - workspaceFolder := data.WorkspaceFolder.ValueString() - if workspaceFolder == "" { - workspaceFolder = filepath.Join(tmpDir, "workspace") - tflog.Debug(ctx, "workspace_folder not specified, using temp dir", map[string]any{"workspace_folder": workspaceFolder}) - } - - opts := eboptions.Options{ - // These options are always required - CacheRepo: data.CacheRepo.ValueString(), - Filesystem: osfs.New("/"), - ForceSafe: false, // This should never be set to true, as this may be running outside of a container! - GetCachedImage: true, // always! - Logger: tfLogFunc(ctx), - Verbose: data.Verbose.ValueBool(), - WorkspaceFolder: workspaceFolder, - - // Options related to compiling the devcontainer - BuildContextPath: data.BuildContextPath.ValueString(), - DevcontainerDir: data.DevcontainerDir.ValueString(), - DevcontainerJSONPath: data.DevcontainerJSONPath.ValueString(), - DockerfilePath: data.DockerfilePath.ValueString(), - DockerConfigBase64: data.DockerConfigBase64.ValueString(), - FallbackImage: data.FallbackImage.ValueString(), - - // These options are required for cloning the Git repo - CacheTTLDays: data.CacheTTLDays.ValueInt64(), - GitURL: data.GitURL.ValueString(), - GitCloneDepth: data.GitCloneDepth.ValueInt64(), - GitCloneSingleBranch: data.GitCloneSingleBranch.ValueBool(), - GitUsername: data.GitUsername.ValueString(), - GitPassword: data.GitPassword.ValueString(), - GitSSHPrivateKeyPath: data.GitSSHPrivateKeyPath.ValueString(), - GitHTTPProxyURL: data.GitHTTPProxyURL.ValueString(), - RemoteRepoBuildMode: data.RemoteRepoBuildMode.ValueBool(), - RemoteRepoDir: filepath.Join(tmpDir, "repo"), - SSLCertBase64: data.SSLCertBase64.ValueString(), - - // Other options - BaseImageCacheDir: data.BaseImageCacheDir.ValueString(), - BinaryPath: envbuilderPath, // needed to reproduce the final layer. - ExitOnBuildFailure: data.ExitOnBuildFailure.ValueBool(), // may wish to do this instead of fallback image? - Insecure: data.Insecure.ValueBool(), // might have internal CAs? - IgnorePaths: tfListToStringSlice(data.IgnorePaths), // may need to be specified? - // The below options are not relevant and are set to their zero value explicitly. - CoderAgentSubsystem: nil, - CoderAgentToken: "", - CoderAgentURL: "", - ExportEnvFile: "", - InitArgs: "", - InitCommand: "", - InitScript: "", - LayerCacheDir: "", - PostStartScriptPath: "", - PushImage: false, // This is only relevant when building. - SetupScript: "", - SkipRebuild: false, - } - - return envbuilder.RunCacheProbe(ctx, opts) -} - -// getRemoteImage fetches the image manifest of the image. -func getRemoteImage(imgRef string) (v1.Image, error) { - ref, err := name.ParseReference(imgRef) - if err != nil { - return nil, fmt.Errorf("parse reference: %w", err) - } - - img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) - if err != nil { - return nil, fmt.Errorf("check remote image: %w", err) - } - - return img, nil -} - -// extractEnvbuilderFromImage reads the image located at imgRef and extracts -// MagicBinaryLocation to destPath. -func extractEnvbuilderFromImage(ctx context.Context, imgRef, destPath string) error { - needle := filepath.Clean(constants.MagicBinaryLocation)[1:] // skip leading '/' - img, err := getRemoteImage(imgRef) - if err != nil { - return fmt.Errorf("check remote image: %w", err) - } - - layers, err := img.Layers() - if err != nil { - return fmt.Errorf("get image layers: %w", err) - } - - // Check the layers in reverse order. The last layers are more likely to - // include the binary. - for i := len(layers) - 1; i >= 0; i-- { - ul, err := layers[i].Uncompressed() - if err != nil { - return fmt.Errorf("get uncompressed layer: %w", err) - } - - tr := tar.NewReader(ul) - for { - th, err := tr.Next() - if err == io.EOF { - break - } - - if err != nil { - return fmt.Errorf("read tar header: %w", err) - } - - name := filepath.Clean(th.Name) - if th.Typeflag != tar.TypeReg { - tflog.Debug(ctx, "skip non-regular file", map[string]any{"name": name, "layer_idx": i + 1}) - continue - } - - if name != needle { - tflog.Debug(ctx, "skip file", map[string]any{"name": name, "layer_idx": i + 1}) - continue - } - - tflog.Debug(ctx, "found file", map[string]any{"name": name, "layer_idx": i + 1}) - if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil { - return fmt.Errorf("create parent directories: %w", err) - } - destF, err := os.Create(destPath) - if err != nil { - return fmt.Errorf("create dest file for writing: %w", err) - } - defer destF.Close() - _, err = io.Copy(destF, tr) - if err != nil { - return fmt.Errorf("copy dest file from image: %w", err) - } - if err := destF.Close(); err != nil { - return fmt.Errorf("close dest file: %w", err) - } - - if err := os.Chmod(destPath, 0o755); err != nil { - return fmt.Errorf("chmod file: %w", err) - } - return nil - } - } - - return fmt.Errorf("extract envbuilder binary from image %q: %w", imgRef, os.ErrNotExist) -} - -// NOTE: the String() method of Terraform values will evalue to `` if unknown. -// Check IsUnknown() first before calling String(). -type stringable interface { - IsUnknown() bool - IsNull() bool - String() string -} - -func appendKnownEnvToList(list types.List, key string, value stringable) types.List { - if value.IsUnknown() || value.IsNull() { - return list - } - val := strings.Trim(value.String(), `"`) - elem := types.StringValue(fmt.Sprintf("%s=%s", key, val)) - list, _ = types.ListValue(types.StringType, append(list.Elements(), elem)) - return list -} - -func tfListToStringSlice(l types.List) []string { - var ss []string - for _, el := range l.Elements() { - if sv, ok := el.(stringable); !ok { - panic(fmt.Sprintf("developer error: element %+v must be stringable", el)) - } else if sv.IsUnknown() { - ss = append(ss, "") - } else { - ss = append(ss, sv.String()) + opts.BinaryPath = envbuilderPath + + // We need a filesystem to work with. + opts.Filesystem = osfs.New("/") + // This should never be set to true, as this may be running outside of a container! + opts.ForceSafe = false + // We always want to get the cached image. + opts.GetCachedImage = true + // Log to the Terraform logger. + opts.Logger = tfutil.TFLogFunc(ctx) + + // We don't require users to set a workspace folder, but maybe there's a + // reason someone may need to. + if opts.WorkspaceFolder == "" { + opts.WorkspaceFolder = filepath.Join(tmpDir, "workspace") + if err := os.MkdirAll(opts.WorkspaceFolder, 0o755); err != nil { + return nil, fmt.Errorf("failed to create workspace folder: %w", err) } - } - return ss -} + tflog.Debug(ctx, "workspace_folder not specified, using temp dir", map[string]any{"workspace_folder": opts.WorkspaceFolder}) + } + + // The below options are not relevant and are set to their zero value + // explicitly. + // They must be set by extra_env to be used in the final builder image. + opts.CoderAgentSubsystem = nil + opts.CoderAgentToken = "" + opts.CoderAgentURL = "" + opts.ExportEnvFile = "" + opts.InitArgs = "" + opts.InitCommand = "" + opts.InitScript = "" + opts.LayerCacheDir = "" + opts.PostStartScriptPath = "" + opts.PushImage = false + opts.SetupScript = "" + opts.SkipRebuild = false -// tfLogFunc is an adapter to envbuilder/log.Func. -func tfLogFunc(ctx context.Context) eblog.Func { - return func(level eblog.Level, format string, args ...any) { - var logFn func(context.Context, string, ...map[string]interface{}) - switch level { - case eblog.LevelTrace: - logFn = tflog.Trace - case eblog.LevelDebug: - logFn = tflog.Debug - case eblog.LevelWarn: - logFn = tflog.Warn - case eblog.LevelError: - logFn = tflog.Error - default: - logFn = tflog.Info - } - logFn(ctx, fmt.Sprintf(format, args...)) - } + return envbuilder.RunCacheProbe(ctx, opts) } diff --git a/internal/provider/cached_image_resource_test.go b/internal/provider/cached_image_resource_test.go index 30bfc0e..86dd581 100644 --- a/internal/provider/cached_image_resource_test.go +++ b/internal/provider/cached_image_resource_test.go @@ -10,105 +10,315 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +// testEnvValue is a multi-line environment variable value that we use in +// tests to ensure that we can handle multi-line values correctly. +var testEnvValue = `bar +baz` + func TestAccCachedImageResource(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() - files := map[string]string{ - ".devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`, - ".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest - RUN date > /date.txt`, - } - deps := setup(ctx, t, files) - deps.ExtraEnv["FOO"] = "bar" - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Initial state: cache has not been seeded. - { - Config: deps.Config(t), - PlanOnly: true, - ExpectNonEmptyPlan: true, + for _, tc := range []struct { + name string + files map[string]string + extraEnv map[string]string + assertEnv func(t *testing.T, deps testDependencies) resource.TestCheckFunc + }{ + { + // This test case is the simplest possible case: a devcontainer.json. + // However, it also makes sure we are able to generate a Dockerfile + // from the devcontainer.json. + name: "devcontainer only", + files: map[string]string{ + ".devcontainer/devcontainer.json": `{"image": "localhost:5000/test-ubuntu:latest"}`, }, - // Should detect that no cached image is present and plan to create the resource. - { - Config: deps.Config(t), - Check: resource.ComposeAggregateTestCheckFunc( - // Computed values MUST be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), - resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "env.0"), - // Cached image should be set to the builder image. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), - // Inputs should still be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), - // Should be empty - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), - ), - ExpectNonEmptyPlan: true, // TODO: check the plan. + extraEnv: map[string]string{ + "CODER_AGENT_TOKEN": "some-token", + "CODER_AGENT_URL": "https://coder.example.com", + "ENVBUILDER_GIT_URL": "https://not.the.real.git/url", + "ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo", + "FOO": testEnvValue, }, - // Re-running plan should have the same effect. - { - Config: deps.Config(t), - Check: resource.ComposeAggregateTestCheckFunc( - // Computed values MUST be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), - resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "env.0"), - // Cached image should be set to the builder image. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), - // Inputs should still be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), - // Should be empty - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), - ), - ExpectNonEmptyPlan: true, // TODO: check the plan. + assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + assertEnv(t, + "CODER_AGENT_TOKEN", "some-token", + "CODER_AGENT_URL", "https://coder.example.com", + "ENVBUILDER_CACHE_REPO", deps.CacheRepo, + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, + "ENVBUILDER_GIT_URL", deps.Repo.URL, + "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", + "ENVBUILDER_VERBOSE", "true", + "FOO", "bar\nbaz", + ), + ) }, - // Now, seed the cache and re-run. We should now successfully create the cached image resource. - { - PreConfig: func() { - seedCache(ctx, t, deps) - }, - Config: deps.Config(t), - Check: resource.ComposeAggregateTestCheckFunc( - // Inputs should still be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), - // Should be empty - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), - // Computed - resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "id", quotedPrefix("sha256:")), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "true"), - resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "image"), - resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "image", quotedPrefix(deps.CacheRepo)), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.0", "FOO=bar"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.1", fmt.Sprintf("ENVBUILDER_CACHE_REPO=%s", deps.CacheRepo)), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.2", fmt.Sprintf("ENVBUILDER_GIT_URL=%s", deps.Repo.URL)), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.3", "ENVBUILDER_REMOTE_REPO_BUILD_MODE=true"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "env.4"), - ), + }, + { + // This test case includes a Dockerfile in addition to the devcontainer.json. + // The Dockerfile writes the current date to a file. This is currently not checked but + // illustrates that a RUN instruction is cached. + name: "devcontainer and Dockerfile", + files: map[string]string{ + ".devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`, + ".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest +RUN date > /date.txt`, + }, + extraEnv: map[string]string{ + "CODER_AGENT_TOKEN": "some-token", + "CODER_AGENT_URL": "https://coder.example.com", + "FOO": testEnvValue, + "ENVBUILDER_GIT_URL": "https://not.the.real.git/url", + "ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo", + }, + assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + assertEnv(t, + "CODER_AGENT_TOKEN", "some-token", + "CODER_AGENT_URL", "https://coder.example.com", + "ENVBUILDER_CACHE_REPO", deps.CacheRepo, + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, + "ENVBUILDER_GIT_URL", deps.Repo.URL, + "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", + "ENVBUILDER_VERBOSE", "true", + "FOO", "bar\nbaz", + ), + ) + }, + }, + { + // This test case ensures that overriding the devcontainer directory works. + name: "different_dir", + files: map[string]string{ + "path/to/.devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`, + "path/to/.devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest + RUN date > /date.txt`, + }, + extraEnv: map[string]string{ + "CODER_AGENT_TOKEN": "some-token", + "CODER_AGENT_URL": "https://coder.example.com", + "FOO": testEnvValue, + "ENVBUILDER_GIT_URL": "https://not.the.real.git/url", + "ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo", + "ENVBUILDER_DEVCONTAINER_DIR": "path/to/.devcontainer", + "ENVBUILDER_DEVCONTAINER_JSON_PATH": "path/to/.devcontainer/devcontainer.json", + "ENVBUILDER_DOCKERFILE_PATH": "path/to/.devcontainer/Dockerfile", + }, + assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + assertEnv(t, + "CODER_AGENT_TOKEN", "some-token", + "CODER_AGENT_URL", "https://coder.example.com", + "ENVBUILDER_CACHE_REPO", deps.CacheRepo, + "ENVBUILDER_DEVCONTAINER_DIR", "path/to/.devcontainer", + "ENVBUILDER_DEVCONTAINER_JSON_PATH", "path/to/.devcontainer/devcontainer.json", + "ENVBUILDER_DOCKERFILE_PATH", "path/to/.devcontainer/Dockerfile", + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, + "ENVBUILDER_GIT_URL", deps.Repo.URL, + "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", + "ENVBUILDER_VERBOSE", "true", + "FOO", "bar\nbaz", + ), + ) + }, + }, + { + // This tests that a multi-stage build works correctly. + name: "multistage_run_copy", + files: map[string]string{ + "Dockerfile": ` + FROM localhost:5000/test-ubuntu:latest AS a + RUN date > /date.txt + FROM localhost:5000/test-ubuntu:latest + COPY --from=a /date.txt /date.txt`, + }, + extraEnv: map[string]string{ + "CODER_AGENT_TOKEN": "some-token", + "CODER_AGENT_URL": "https://coder.example.com", + "FOO": testEnvValue, + "ENVBUILDER_GIT_URL": "https://not.the.real.git/url", + "ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo", + "ENVBUILDER_DOCKERFILE_PATH": "Dockerfile", + }, + assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + assertEnv(t, + "CODER_AGENT_TOKEN", "some-token", + "CODER_AGENT_URL", "https://coder.example.com", + "ENVBUILDER_CACHE_REPO", deps.CacheRepo, + "ENVBUILDER_DOCKERFILE_PATH", "Dockerfile", + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, + "ENVBUILDER_GIT_URL", deps.Repo.URL, + "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", + "ENVBUILDER_VERBOSE", "true", + "FOO", "bar\nbaz", + ), + ) }, - // Should produce an empty plan after apply - { - Config: deps.Config(t), - PlanOnly: true, + }, + { + // This tests correct handling of the difference in permissions between + // the provider and the image when running a COPY instruction. + // Added to verify fix for coder/terraform-provider-envbuilder#43 + name: "copy_perms", + files: map[string]string{ + "Dockerfile": ` + FROM localhost:5000/test-ubuntu:latest AS a + COPY date.txt /date.txt + FROM localhost:5000/test-ubuntu:latest + COPY --from=a /date.txt /date.txt`, + "date.txt": fmt.Sprintf("%d", time.Now().Unix()), + }, + extraEnv: map[string]string{ + "CODER_AGENT_TOKEN": "some-token", + "CODER_AGENT_URL": "https://coder.example.com", + "FOO": testEnvValue, + "ENVBUILDER_GIT_URL": "https://not.the.real.git/url", + "ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo", + "ENVBUILDER_DOCKERFILE_PATH": "Dockerfile", }, - // Ensure idempotence in this state! - { - Config: deps.Config(t), - PlanOnly: true, + assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + assertEnv(t, + "CODER_AGENT_TOKEN", "some-token", + "CODER_AGENT_URL", "https://coder.example.com", + "ENVBUILDER_CACHE_REPO", deps.CacheRepo, + "ENVBUILDER_DOCKERFILE_PATH", "Dockerfile", + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, + "ENVBUILDER_GIT_URL", deps.Repo.URL, + "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", + "ENVBUILDER_VERBOSE", "true", + "FOO", "bar\nbaz", + ), + ) }, }, - }) + } { + t.Run(tc.name, func(t *testing.T) { + //nolint: paralleltest + deps := setup(ctx, t, tc.extraEnv, tc.files) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // 1) Initial state: cache has not been seeded. + { + Config: deps.Config(t), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + // 2) Should detect that no cached image is present and plan to create the resource. + { + Config: deps.Config(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Computed values MUST be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), + // Cached image should be set to the builder image. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), + // Inputs should still be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), + // Should be empty + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), + // Environment variables + tc.assertEnv(t, deps), + ), + ExpectNonEmptyPlan: true, // TODO: check the plan. + }, + // 3) Re-running plan should have the same effect. + { + Config: deps.Config(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Computed values MUST be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), + // Cached image should be set to the builder image. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), + // Inputs should still be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), + // Should be empty + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), + // Environment variables + tc.assertEnv(t, deps), + ), + ExpectNonEmptyPlan: true, // TODO: check the plan. + }, + // 4) Now, seed the cache and re-run. We should now successfully create the cached image resource. + { + PreConfig: func() { + seedCache(ctx, t, deps) + }, + Config: deps.Config(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Inputs should still be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), + // Should be empty + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), + // Computed + resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "id", quotedPrefix("sha256:")), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "true"), + resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "image"), + resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "image", quotedPrefix(deps.CacheRepo)), + // Environment variables + tc.assertEnv(t, deps), + ), + }, + // 5) Should produce an empty plan after apply + { + Config: deps.Config(t), + PlanOnly: true, + }, + // 6) Ensure idempotence in this state! + { + Config: deps.Config(t), + PlanOnly: true, + }, + }, + }) + }) + } +} + +// assertEnv is a test helper that checks the environment variables, in order, +// on both the env and env_map attributes of the cached image resource. +func assertEnv(t *testing.T, kvs ...string) resource.TestCheckFunc { + t.Helper() + if len(kvs)%2 != 0 { + t.Fatalf("assertEnv: expected an even number of key-value pairs, got %d", len(kvs)) + } + + funcs := make([]resource.TestCheckFunc, 0) + for i := 0; i < len(kvs); i += 2 { + resKey := fmt.Sprintf("env.%d", len(funcs)) + resVal := fmt.Sprintf("%s=%s", kvs[i], kvs[i+1]) + fn := resource.TestCheckResourceAttr("envbuilder_cached_image.test", resKey, resVal) + funcs = append(funcs, fn) + } + + lastKey := fmt.Sprintf("env.%d", len(funcs)) + lastFn := resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", lastKey) + funcs = append(funcs, lastFn) + + for i := 0; i < len(kvs); i += 2 { + resKey := fmt.Sprintf("env_map.%s", kvs[i]) + fn := resource.TestCheckResourceAttr("envbuilder_cached_image.test", resKey, kvs[i+1]) + funcs = append(funcs, fn) + } + + return resource.ComposeAggregateTestCheckFunc(funcs...) } diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go new file mode 100644 index 0000000..acc69f2 --- /dev/null +++ b/internal/provider/helpers.go @@ -0,0 +1,282 @@ +package provider + +import ( + "fmt" + "slices" + "strings" + + eboptions "github.com/coder/envbuilder/options" + "github.com/coder/serpent" + "github.com/coder/terraform-provider-envbuilder/internal/tfutil" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/spf13/pflag" +) + +const ( + envbuilderOptionPrefix = "ENVBUILDER_" +) + +// nonOverrideOptions are options that cannot be overridden by extra_env. +var nonOverrideOptions = map[string]bool{ + "ENVBUILDER_CACHE_REPO": true, + "ENVBUILDER_GIT_URL": true, +} + +// optionsFromDataModel converts a CachedImageResourceModel into a corresponding set of +// Envbuilder options. It returns the options and any diagnostics encountered. +func optionsFromDataModel(data CachedImageResourceModel) (eboptions.Options, diag.Diagnostics) { + var diags diag.Diagnostics + var opts eboptions.Options + + // Required options. Cannot be overridden by extra_env. + opts.CacheRepo = data.CacheRepo.ValueString() + opts.GitURL = data.GitURL.ValueString() + + // Other options can be overridden by extra_env, with a warning. + // Keep track of which options are set from the data model so we + // can check if they are being overridden. + providerOpts := make(map[string]bool) + + if !data.BaseImageCacheDir.IsNull() { + providerOpts["ENVBUILDER_BASE_IMAGE_CACHE_DIR"] = true + opts.BaseImageCacheDir = data.BaseImageCacheDir.ValueString() + } + + if !data.BuildContextPath.IsNull() { + providerOpts["ENVBUILDER_BUILD_CONTEXT_PATH"] = true + opts.BuildContextPath = data.BuildContextPath.ValueString() + } + + if !data.BuildSecrets.IsNull() { + providerOpts["ENVBUILDER_BUILD_SECRETS"] = true + + // Depending on use case, users might want to provide build secrets as a map or a list of strings. + // The string list option is supported by extra_env, so we support the map option here. Envbuilder + // expects a list of strings, so we convert the map to a list of strings here. + buildSecretMap := tfutil.TFMapToStringMap(data.BuildSecrets) + buildSecretSlice := make([]string, 0, len(buildSecretMap)) + for k, v := range buildSecretMap { + buildSecretSlice = append(buildSecretSlice, fmt.Sprintf("%s=%s", k, v)) + } + slices.Sort(buildSecretSlice) + + opts.BuildSecrets = buildSecretSlice + } + + if !data.CacheTTLDays.IsNull() { + providerOpts["ENVBUILDER_CACHE_TTL_DAYS"] = true + opts.CacheTTLDays = data.CacheTTLDays.ValueInt64() + } + + if !data.DevcontainerDir.IsNull() { + providerOpts["ENVBUILDER_DEVCONTAINER_DIR"] = true + opts.DevcontainerDir = data.DevcontainerDir.ValueString() + } + + if !data.DevcontainerJSONPath.IsNull() { + providerOpts["ENVBUILDER_DEVCONTAINER_JSON_PATH"] = true + opts.DevcontainerJSONPath = data.DevcontainerJSONPath.ValueString() + } + + if !data.DockerfilePath.IsNull() { + providerOpts["ENVBUILDER_DOCKERFILE_PATH"] = true + opts.DockerfilePath = data.DockerfilePath.ValueString() + } + + if !data.DockerConfigBase64.IsNull() { + providerOpts["ENVBUILDER_DOCKER_CONFIG_BASE64"] = true + opts.DockerConfigBase64 = data.DockerConfigBase64.ValueString() + } + + if !data.ExitOnBuildFailure.IsNull() { + providerOpts["ENVBUILDER_EXIT_ON_BUILD_FAILURE"] = true + opts.ExitOnBuildFailure = data.ExitOnBuildFailure.ValueBool() + } + + if !data.FallbackImage.IsNull() { + providerOpts["ENVBUILDER_FALLBACK_IMAGE"] = true + opts.FallbackImage = data.FallbackImage.ValueString() + } + + if !data.GitCloneDepth.IsNull() { + providerOpts["ENVBUILDER_GIT_CLONE_DEPTH"] = true + opts.GitCloneDepth = data.GitCloneDepth.ValueInt64() + } + + if !data.GitCloneSingleBranch.IsNull() { + providerOpts["ENVBUILDER_GIT_CLONE_SINGLE_BRANCH"] = true + opts.GitCloneSingleBranch = data.GitCloneSingleBranch.ValueBool() + } + + if !data.GitHTTPProxyURL.IsNull() { + providerOpts["ENVBUILDER_GIT_HTTP_PROXY_URL"] = true + opts.GitHTTPProxyURL = data.GitHTTPProxyURL.ValueString() + } + + if !data.GitSSHPrivateKeyPath.IsNull() { + providerOpts["ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH"] = true + opts.GitSSHPrivateKeyPath = data.GitSSHPrivateKeyPath.ValueString() + } + + if !data.GitSSHPrivateKeyBase64.IsNull() { + providerOpts["ENVBUILDER_GIT_SSH_PRIVATE_KEY_BASE64"] = true + opts.GitSSHPrivateKeyBase64 = data.GitSSHPrivateKeyBase64.ValueString() + } + + if !data.GitUsername.IsNull() { + providerOpts["ENVBUILDER_GIT_USERNAME"] = true + opts.GitUsername = data.GitUsername.ValueString() + } + + if !data.GitPassword.IsNull() { + providerOpts["ENVBUILDER_GIT_PASSWORD"] = true + opts.GitPassword = data.GitPassword.ValueString() + } + + if !data.IgnorePaths.IsNull() { + providerOpts["ENVBUILDER_IGNORE_PATHS"] = true + opts.IgnorePaths = tfutil.TFListToStringSlice(data.IgnorePaths) + } + + if !data.Insecure.IsNull() { + providerOpts["ENVBUILDER_INSECURE"] = true + opts.Insecure = data.Insecure.ValueBool() + } + + if data.RemoteRepoBuildMode.IsNull() { + opts.RemoteRepoBuildMode = true + } else { + providerOpts["ENVBUILDER_REMOTE_REPO_BUILD_MODE"] = true + opts.RemoteRepoBuildMode = data.RemoteRepoBuildMode.ValueBool() + } + + if !data.SSLCertBase64.IsNull() { + providerOpts["ENVBUILDER_SSL_CERT_BASE64"] = true + opts.SSLCertBase64 = data.SSLCertBase64.ValueString() + } + + if !data.Verbose.IsNull() { + providerOpts["ENVBUILDER_VERBOSE"] = true + opts.Verbose = data.Verbose.ValueBool() + } + + if !data.WorkspaceFolder.IsNull() { + providerOpts["ENVBUILDER_WORKSPACE_FOLDER"] = true + opts.WorkspaceFolder = data.WorkspaceFolder.ValueString() + } + + // convert extraEnv to a map for ease of use. + extraEnv := make(map[string]string) + for k, v := range data.ExtraEnv.Elements() { + extraEnv[k] = tfutil.TFValueToString(v) + } + diags = append(diags, overrideOptionsFromExtraEnv(&opts, extraEnv, providerOpts)...) + + if opts.GitSSHPrivateKeyPath != "" && opts.GitSSHPrivateKeyBase64 != "" { + diags.AddError("Cannot set more than one git ssh private key option", + "Both ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH and ENVBUILDER_GIT_SSH_PRIVATE_KEY_BASE64 have been set.") + } + + return opts, diags +} + +// overrideOptionsFromExtraEnv overrides the options in opts with values from extraEnv. +// It returns any diagnostics encountered. +// It will not override certain options, such as ENVBUILDER_CACHE_REPO and ENVBUILDER_GIT_URL. +func overrideOptionsFromExtraEnv(opts *eboptions.Options, extraEnv map[string]string, providerOpts map[string]bool) diag.Diagnostics { + var diags diag.Diagnostics + // Make a map of the options for easy lookup. + optsMap := make(map[string]pflag.Value) + for _, opt := range opts.CLI() { + optsMap[opt.Env] = opt.Value + } + for key, val := range extraEnv { + opt, found := optsMap[key] + if !found { + // ignore unknown keys + continue + } + + if nonOverrideOptions[key] { + diags.AddAttributeWarning(path.Root("extra_env"), + "Cannot override required environment variable", + fmt.Sprintf("The key %q in extra_env cannot be overridden.", key), + ) + continue + } + + // Check if the option was set on the provider data model and generate a warning if so. + if providerOpts[key] { + diags.AddAttributeWarning(path.Root("extra_env"), + "Overriding provider environment variable", + fmt.Sprintf("The key %q in extra_env overrides an option set on the provider.", key), + ) + } + + // XXX: workaround for serpent behaviour where calling Set() on a + // string slice will append instead of replace: set to empty first. + if _, ok := optsMap[key].(*serpent.StringArray); ok { + _ = optsMap[key].Set("") + } + + if err := opt.Set(val); err != nil { + diags.AddAttributeError(path.Root("extra_env"), + "Invalid value for environment variable", + fmt.Sprintf("The key %q in extra_env has an invalid value: %s", key, err), + ) + } + } + return diags +} + +// computeEnvFromOptions computes the environment variables to set based on the +// options in opts and the extra environment variables in extraEnv. +// It returns the computed environment variables as a map. +// It will not set certain options, such as ENVBUILDER_CACHE_REPO and ENVBUILDER_GIT_URL. +// It will also not handle legacy Envbuilder options (i.e. those not prefixed with ENVBUILDER_). +func computeEnvFromOptions(opts eboptions.Options, extraEnv map[string]string) map[string]string { + for _, opt := range opts.CLI() { + if opt.Env == "" { + continue + } + } + + computed := make(map[string]string) + for _, opt := range opts.CLI() { + if opt.Env == "" { + continue + } + // TODO: remove this check once support for legacy options is removed. + // Only set the environment variables from opts that are not legacy options. + // Legacy options are those that are not prefixed with ENVBUILDER_. + // While we can detect when a legacy option is set, overriding it becomes + // problematic. Erring on the side of caution, we will not override legacy options. + if !strings.HasPrefix(opt.Env, envbuilderOptionPrefix) { + continue + } + var val string + if sa, ok := opt.Value.(*serpent.StringArray); ok { + val = strings.Join(sa.GetSlice(), ",") + } else { + val = opt.Value.String() + } + + switch val { + case "", "false", "0": + // Skip zero values. + continue + } + computed[opt.Env] = val + } + + // Merge in extraEnv, which may override values from opts. + // Skip any keys that are envbuilder options. + for key, val := range extraEnv { + if strings.HasPrefix(key, envbuilderOptionPrefix) { + continue + } + computed[key] = val + } + return computed +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d444c23..cfc0c61 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -36,6 +36,9 @@ func (p *EnvbuilderProvider) Metadata(ctx context.Context, req provider.Metadata func (p *EnvbuilderProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{}, + MarkdownDescription: ` +The Envbuilder provider can be used to check for the presence of a container image previously built by [Envbuilder](https://github.com/coder/envbuilder). +This allows re-using a previously built image pushed to a container registry without having to rebuild it.`, } } diff --git a/internal/provider/provider_internal_test.go b/internal/provider/provider_internal_test.go new file mode 100644 index 0000000..c1ae983 --- /dev/null +++ b/internal/provider/provider_internal_test.go @@ -0,0 +1,421 @@ +package provider + +import ( + "testing" + + eboptions "github.com/coder/envbuilder/options" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/stretchr/testify/assert" +) + +func Test_optionsFromDataModel(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + data CachedImageResourceModel + expectOpts eboptions.Options + expectNumErrorDiags int + expectNumWarningDiags int + }{ + { + name: "required only", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + }, + expectOpts: eboptions.Options{ + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + RemoteRepoBuildMode: true, + }, + }, + { + name: "all options without extra_env", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + BaseImageCacheDir: basetypes.NewStringValue("/tmp/cache"), + BuildContextPath: basetypes.NewStringValue("."), + BuildSecrets: basetypes.NewMapValueMust(basetypes.StringType{}, map[string]attr.Value{ + "FOO": basetypes.NewStringValue("bar"), + "BAZ": basetypes.NewStringValue("qux"), + }), + CacheTTLDays: basetypes.NewInt64Value(7), + DevcontainerDir: basetypes.NewStringValue(".devcontainer"), + DevcontainerJSONPath: basetypes.NewStringValue(".devcontainer/devcontainer.json"), + DockerfilePath: basetypes.NewStringValue("Dockerfile"), + DockerConfigBase64: basetypes.NewStringValue("some base64"), + ExitOnBuildFailure: basetypes.NewBoolValue(true), + // ExtraEnv: map[string]basetypes.Value{}, + FallbackImage: basetypes.NewStringValue("fallback"), + GitCloneDepth: basetypes.NewInt64Value(1), + GitCloneSingleBranch: basetypes.NewBoolValue(true), + GitHTTPProxyURL: basetypes.NewStringValue("http://proxy"), + GitPassword: basetypes.NewStringValue("password"), + GitSSHPrivateKeyPath: basetypes.NewStringValue("/tmp/id_rsa"), + GitUsername: basetypes.NewStringValue("user"), + IgnorePaths: listValue("ignore", "paths"), + Insecure: basetypes.NewBoolValue(true), + RemoteRepoBuildMode: basetypes.NewBoolValue(false), + SSLCertBase64: basetypes.NewStringValue("cert"), + Verbose: basetypes.NewBoolValue(true), + WorkspaceFolder: basetypes.NewStringValue("workspace"), + }, + expectOpts: eboptions.Options{ + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + BaseImageCacheDir: "/tmp/cache", + BuildContextPath: ".", + BuildSecrets: []string{"BAZ=qux", "FOO=bar"}, // Sorted + CacheTTLDays: 7, + DevcontainerDir: ".devcontainer", + DevcontainerJSONPath: ".devcontainer/devcontainer.json", + DockerfilePath: "Dockerfile", + DockerConfigBase64: "some base64", + ExitOnBuildFailure: true, + FallbackImage: "fallback", + GitCloneDepth: 1, + GitCloneSingleBranch: true, + GitHTTPProxyURL: "http://proxy", + GitPassword: "password", + GitSSHPrivateKeyPath: "/tmp/id_rsa", + GitUsername: "user", + IgnorePaths: []string{"ignore", "paths"}, + Insecure: true, + RemoteRepoBuildMode: false, + SSLCertBase64: "cert", + Verbose: true, + WorkspaceFolder: "workspace", + }, + }, + { + name: "extra env override", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + ExtraEnv: extraEnvMap(t, + "CODER_AGENT_TOKEN", "token", + "CODER_AGENT_URL", "http://coder", + "FOO", "bar", + "ENVBUILDER_BUILD_SECRETS", "FOO=bar,BAZ=qux", + ), + }, + expectOpts: eboptions.Options{ + BuildSecrets: []string{"FOO=bar", "BAZ=qux"}, + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + RemoteRepoBuildMode: true, + CoderAgentToken: "token", + CoderAgentURL: "http://coder", + }, + }, + { + name: "extra_env override warnings", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + BaseImageCacheDir: basetypes.NewStringValue("/tmp/cache"), + BuildContextPath: basetypes.NewStringValue("."), + BuildSecrets: basetypes.NewMapValueMust(basetypes.StringType{}, map[string]attr.Value{ + "FOO": basetypes.NewStringValue("bar"), + }), + CacheTTLDays: basetypes.NewInt64Value(7), + DevcontainerDir: basetypes.NewStringValue(".devcontainer"), + DevcontainerJSONPath: basetypes.NewStringValue(".devcontainer/devcontainer.json"), + DockerfilePath: basetypes.NewStringValue("Dockerfile"), + DockerConfigBase64: basetypes.NewStringValue("some base64"), + ExitOnBuildFailure: basetypes.NewBoolValue(true), + // ExtraEnv: map[string]basetypes.Value{}, + FallbackImage: basetypes.NewStringValue("fallback"), + GitCloneDepth: basetypes.NewInt64Value(1), + GitCloneSingleBranch: basetypes.NewBoolValue(true), + GitHTTPProxyURL: basetypes.NewStringValue("http://proxy"), + GitPassword: basetypes.NewStringValue("password"), + GitSSHPrivateKeyPath: basetypes.NewStringValue("/tmp/id_rsa"), + GitUsername: basetypes.NewStringValue("user"), + IgnorePaths: listValue("ignore", "paths"), + Insecure: basetypes.NewBoolValue(true), + RemoteRepoBuildMode: basetypes.NewBoolValue(false), + SSLCertBase64: basetypes.NewStringValue("cert"), + Verbose: basetypes.NewBoolValue(true), + WorkspaceFolder: basetypes.NewStringValue("workspace"), + ExtraEnv: extraEnvMap(t, + "ENVBUILDER_BUILD_SECRETS", "FOO=bar,BAZ=qux", + "ENVBUILDER_CACHE_REPO", "override", + "ENVBUILDER_GIT_URL", "override", + "ENVBUILDER_BASE_IMAGE_CACHE_DIR", "override", + "ENVBUILDER_BUILD_CONTEXT_PATH", "override", + "ENVBUILDER_CACHE_TTL_DAYS", "8", + "ENVBUILDER_DEVCONTAINER_DIR", "override", + "ENVBUILDER_DEVCONTAINER_JSON_PATH", "override", + "ENVBUILDER_DOCKERFILE_PATH", "override", + "ENVBUILDER_DOCKER_CONFIG_BASE64", "override", + "ENVBUILDER_EXIT_ON_BUILD_FAILURE", "false", + "ENVBUILDER_FALLBACK_IMAGE", "override", + "ENVBUILDER_GIT_CLONE_DEPTH", "2", + "ENVBUILDER_GIT_CLONE_SINGLE_BRANCH", "false", + "ENVBUILDER_GIT_HTTP_PROXY_URL", "override", + "ENVBUILDER_GIT_PASSWORD", "override", + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", "override", + "ENVBUILDER_GIT_USERNAME", "override", + "ENVBUILDER_IGNORE_PATHS", "override", + "ENVBUILDER_INSECURE", "false", + "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", + "ENVBUILDER_SSL_CERT_BASE64", "override", + "ENVBUILDER_VERBOSE", "false", + "ENVBUILDER_WORKSPACE_FOLDER", "override", + "FOO", "bar", + ), + }, + expectOpts: eboptions.Options{ + // not overridden + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + // overridden + BaseImageCacheDir: "override", + BuildContextPath: "override", + BuildSecrets: []string{"FOO=bar", "BAZ=qux"}, + CacheTTLDays: 8, + DevcontainerDir: "override", + DevcontainerJSONPath: "override", + DockerfilePath: "override", + DockerConfigBase64: "override", + ExitOnBuildFailure: false, + FallbackImage: "override", + GitCloneDepth: 2, + GitCloneSingleBranch: false, + GitHTTPProxyURL: "override", + GitPassword: "override", + GitSSHPrivateKeyPath: "override", + GitUsername: "override", + IgnorePaths: []string{"override"}, + Insecure: false, + RemoteRepoBuildMode: true, + SSLCertBase64: "override", + Verbose: false, + WorkspaceFolder: "override", + }, + expectNumWarningDiags: 24, + }, + { + name: "extra_env override errors", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + ExtraEnv: extraEnvMap(t, + "ENVBUILDER_CACHE_TTL_DAYS", "not a number", + "ENVBUILDER_VERBOSE", "not a bool", + "FOO", "bar", + ), + }, + expectOpts: eboptions.Options{ + // not overridden + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + RemoteRepoBuildMode: true, + }, + expectNumErrorDiags: 2, + }, + { + name: "errors when git ssh private key path and base64 are set", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + GitSSHPrivateKeyPath: basetypes.NewStringValue("/tmp/id_rsa"), + GitSSHPrivateKeyBase64: basetypes.NewStringValue("cHJpdmF0ZUtleQo="), + }, + expectOpts: eboptions.Options{ + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + RemoteRepoBuildMode: true, + GitSSHPrivateKeyPath: "/tmp/id_rsa", + GitSSHPrivateKeyBase64: "cHJpdmF0ZUtleQo=", + }, + expectNumErrorDiags: 1, + }, + { + name: "extra_env override errors when git ssh private key path and base64 are set", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + GitSSHPrivateKeyBase64: basetypes.NewStringValue("cHJpdmF0ZUtleQo="), + ExtraEnv: extraEnvMap(t, + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", "/tmp/id_rsa", + ), + }, + expectOpts: eboptions.Options{ + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + RemoteRepoBuildMode: true, + GitSSHPrivateKeyPath: "/tmp/id_rsa", + GitSSHPrivateKeyBase64: "cHJpdmF0ZUtleQo=", + }, + expectNumErrorDiags: 1, + }, + { + name: "required only with base64 ssh key", + data: CachedImageResourceModel{ + BuilderImage: basetypes.NewStringValue("envbuilder:latest"), + CacheRepo: basetypes.NewStringValue("localhost:5000/cache"), + GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"), + GitSSHPrivateKeyBase64: basetypes.NewStringValue("cHJpdmF0ZUtleQo="), + }, + expectOpts: eboptions.Options{ + CacheRepo: "localhost:5000/cache", + GitURL: "git@git.local/devcontainer.git", + RemoteRepoBuildMode: true, + GitSSHPrivateKeyBase64: "cHJpdmF0ZUtleQo=", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + actual, diags := optionsFromDataModel(tc.data) + assert.Equal(t, tc.expectNumErrorDiags, diags.ErrorsCount()) + assert.Equal(t, tc.expectNumWarningDiags, diags.WarningsCount()) + assert.EqualValues(t, tc.expectOpts, actual) + }) + } +} + +func Test_computeEnvFromOptions(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + opts eboptions.Options + extraEnv map[string]string + expectEnv map[string]string + }{ + { + name: "empty", + opts: eboptions.Options{}, + expectEnv: map[string]string{}, + }, + { + name: "all options", + opts: eboptions.Options{ + BaseImageCacheDir: "string", + BinaryPath: "string", + BuildContextPath: "string", + BuildSecrets: []string{"FOO=bar", "BAZ=qux"}, + CacheRepo: "string", + CacheTTLDays: 1, + CoderAgentSubsystem: []string{"one", "two"}, + CoderAgentToken: "string", + CoderAgentURL: "string", + DevcontainerDir: "string", + DevcontainerJSONPath: "string", + DockerConfigBase64: "string", + DockerfilePath: "string", + ExitOnBuildFailure: true, + ExportEnvFile: "string", + FallbackImage: "string", + ForceSafe: true, + GetCachedImage: true, + GitCloneDepth: 1, + GitCloneSingleBranch: true, + GitHTTPProxyURL: "string", + GitPassword: "string", + GitSSHPrivateKeyPath: "string", + GitURL: "string", + GitUsername: "string", + IgnorePaths: []string{"one", "two"}, + InitArgs: "string", + InitCommand: "string", + InitScript: "string", + Insecure: true, + LayerCacheDir: "string", + PostStartScriptPath: "string", + PushImage: true, + RemoteRepoBuildMode: true, + SetupScript: "string", + SkipRebuild: true, + SSLCertBase64: "string", + Verbose: true, + WorkspaceFolder: "string", + }, + extraEnv: map[string]string{ + "ENVBUILDER_SOMETHING": "string", // should be ignored + "FOO": "bar", // should be included + }, + expectEnv: map[string]string{ + "ENVBUILDER_BASE_IMAGE_CACHE_DIR": "string", + "ENVBUILDER_BINARY_PATH": "string", + "ENVBUILDER_BUILD_CONTEXT_PATH": "string", + "ENVBUILDER_BUILD_SECRETS": "FOO=bar,BAZ=qux", + "ENVBUILDER_CACHE_REPO": "string", + "ENVBUILDER_CACHE_TTL_DAYS": "1", + "ENVBUILDER_DEVCONTAINER_DIR": "string", + "ENVBUILDER_DEVCONTAINER_JSON_PATH": "string", + "ENVBUILDER_DOCKER_CONFIG_BASE64": "string", + "ENVBUILDER_DOCKERFILE_PATH": "string", + "ENVBUILDER_EXIT_ON_BUILD_FAILURE": "true", + "ENVBUILDER_EXPORT_ENV_FILE": "string", + "ENVBUILDER_FALLBACK_IMAGE": "string", + "ENVBUILDER_FORCE_SAFE": "true", + "ENVBUILDER_GET_CACHED_IMAGE": "true", + "ENVBUILDER_GIT_CLONE_DEPTH": "1", + "ENVBUILDER_GIT_CLONE_SINGLE_BRANCH": "true", + "ENVBUILDER_GIT_HTTP_PROXY_URL": "string", + "ENVBUILDER_GIT_PASSWORD": "string", + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": "string", + "ENVBUILDER_GIT_URL": "string", + "ENVBUILDER_GIT_USERNAME": "string", + "ENVBUILDER_IGNORE_PATHS": "one,two", + "ENVBUILDER_INIT_ARGS": "string", + "ENVBUILDER_INIT_COMMAND": "string", + "ENVBUILDER_INIT_SCRIPT": "string", + "ENVBUILDER_INSECURE": "true", + "ENVBUILDER_LAYER_CACHE_DIR": "string", + "ENVBUILDER_POST_START_SCRIPT_PATH": "string", + "ENVBUILDER_PUSH_IMAGE": "true", + "ENVBUILDER_REMOTE_REPO_BUILD_MODE": "true", + "ENVBUILDER_SETUP_SCRIPT": "string", + "ENVBUILDER_SKIP_REBUILD": "true", + "ENVBUILDER_SSL_CERT_BASE64": "string", + "ENVBUILDER_VERBOSE": "true", + "ENVBUILDER_WORKSPACE_FOLDER": "string", + "FOO": "bar", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if tc.extraEnv == nil { + tc.extraEnv = map[string]string{} + } + actual := computeEnvFromOptions(tc.opts, tc.extraEnv) + assert.EqualValues(t, tc.expectEnv, actual) + }) + } +} + +func listValue(vs ...string) basetypes.ListValue { + vals := make([]attr.Value, len(vs)) + for i, s := range vs { + vals[i] = basetypes.NewStringValue(s) + } + return basetypes.NewListValueMust(basetypes.StringType{}, vals) +} + +func extraEnvMap(t *testing.T, kvs ...string) basetypes.MapValue { + t.Helper() + if len(kvs)%2 != 0 { + t.Fatalf("extraEnvMap: expected even number of key-value pairs, got %d", len(kvs)) + } + vals := make(map[string]attr.Value) + for i := 0; i < len(kvs); i += 2 { + vals[kvs[i]] = basetypes.NewStringValue(kvs[i+1]) + } + return basetypes.NewMapValueMust(basetypes.StringType{}, vals) +} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index cfe32a9..1df4490 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -3,6 +3,7 @@ package provider import ( "bufio" "context" + "encoding/base64" "fmt" "io" "os" @@ -35,10 +36,11 @@ var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServe // testDependencies contain information about stuff the test depends on. type testDependencies struct { - BuilderImage string - CacheRepo string - ExtraEnv map[string]string - Repo testGitRepoSSH + BuilderImage string + CacheRepo string + DockerConfigBase64 string + ExtraEnv map[string]string + Repo testGitRepoSSH } // Config generates a valid Terraform config file from the dependencies. @@ -47,16 +49,17 @@ func (d *testDependencies) Config(t testing.TB) string { tpl := `provider envbuilder {} resource "envbuilder_cached_image" "test" { - builder_image = {{ quote .BuilderImage }} + builder_image = {{ quote .BuilderImage }} cache_repo = {{ quote .CacheRepo }} + docker_config_base64 = {{ quote .DockerConfigBase64 }} + git_url = {{ quote .Repo.URL }} extra_env = { + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": {{ quote .Repo.Key }} + "ENVBUILDER_VERBOSE": true {{ range $k, $v := .ExtraEnv }} {{ quote $k }}: {{ quote $v }} {{ end }} } - git_url = {{ quote .Repo.URL }} - git_ssh_private_key_path = {{ quote .Repo.Key }} - verbose = true }` fm := template.FuncMap{"quote": quote} @@ -71,26 +74,36 @@ func quote(s string) string { return fmt.Sprintf("%q", s) } -func setup(ctx context.Context, t testing.TB, files map[string]string) testDependencies { +func setup(ctx context.Context, t testing.TB, extraEnv, files map[string]string) testDependencies { t.Helper() envbuilderImage := getEnvOrDefault("ENVBUILDER_IMAGE", "localhost:5000/envbuilder") envbuilderVersion := getEnvOrDefault("ENVBUILDER_VERSION", "latest") envbuilderImageRef := envbuilderImage + ":" + envbuilderVersion - // TODO: envbuilder creates /.envbuilder/bin/envbuilder owned by root:root which we are unable to clean up. - // This causes tests to fail. + testUsername := "testuser" + testPassword := "testpassword" + testAuthBase64 := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", testUsername, testPassword))) regDir := t.TempDir() - reg := registrytest.New(t, regDir) + reg := registrytest.New(t, regDir, registrytest.BasicAuthMW(t, testUsername, testPassword)) repoDir := setupGitRepo(t, files) gitRepo := serveGitRepoSSH(ctx, t, repoDir) + dockerConfigJSON := fmt.Sprintf(`{ + "auths": { + "%s": { + "auth": "%s", + } + } + }`, reg, testAuthBase64) + dockerConfigJSONBase64 := base64.StdEncoding.EncodeToString([]byte(dockerConfigJSON)) return testDependencies{ - BuilderImage: envbuilderImageRef, - CacheRepo: reg + "/test", - ExtraEnv: make(map[string]string), - Repo: gitRepo, + BuilderImage: envbuilderImageRef, + CacheRepo: reg + "/test", + ExtraEnv: extraEnv, + Repo: gitRepo, + DockerConfigBase64: dockerConfigJSONBase64, } } @@ -106,18 +119,39 @@ func seedCache(ctx context.Context, t testing.TB, deps testDependencies) { ensureImage(ctx, t, cli, deps.BuilderImage) + // Set up env for envbuilder + seedEnv := map[string]string{ + "ENVBUILDER_CACHE_REPO": deps.CacheRepo, + "ENVBUILDER_EXIT_ON_BUILD_FAILURE": "true", + "ENVBUILDER_INIT_SCRIPT": "exit", + "ENVBUILDER_PUSH_IMAGE": "true", + "ENVBUILDER_VERBOSE": "true", + "ENVBUILDER_GIT_URL": deps.Repo.URL, + "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": "/id_ed25519", + "ENVBUILDER_DOCKER_CONFIG_BASE64": deps.DockerConfigBase64, + } + + for k, v := range deps.ExtraEnv { + if !strings.HasPrefix(k, envbuilderOptionPrefix) { + continue + } + if _, ok := seedEnv[k]; ok { + continue + } + seedEnv[k] = v + } + + seedDockerEnv := make([]string, 0) + for k, v := range seedEnv { + seedDockerEnv = append(seedDockerEnv, k+"="+v) + } + + t.Logf("running envbuilder to seed cache with args: %v", seedDockerEnv) + // Run envbuilder using this dir as a local layer cache ctr, err := cli.ContainerCreate(ctx, &container.Config{ Image: deps.BuilderImage, - Env: []string{ - "ENVBUILDER_CACHE_REPO=" + deps.CacheRepo, - "ENVBUILDER_EXIT_ON_BUILD_FAILURE=true", - "ENVBUILDER_INIT_SCRIPT=exit", - "ENVBUILDER_PUSH_IMAGE=true", - "ENVBUILDER_VERBOSE=true", - "ENVBUILDER_GIT_URL=" + deps.Repo.URL, - "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH=/id_ed25519", - }, + Env: seedDockerEnv, Labels: map[string]string{ testContainerLabel: "true", }, @@ -160,7 +194,7 @@ SCANLOGS: } log := scanner.Text() t.Logf("envbuilder: %s", log) - if strings.Contains(log, "=== Running the init command") { + if strings.Contains(log, "=== Running init command") { break SCANLOGS } } diff --git a/internal/tfutil/tfutil.go b/internal/tfutil/tfutil.go new file mode 100644 index 0000000..3366b6f --- /dev/null +++ b/internal/tfutil/tfutil.go @@ -0,0 +1,92 @@ +package tfutil + +import ( + "context" + "fmt" + "sort" + "strings" + + "github.com/coder/envbuilder/log" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// TFValueToString converts an attr.Value to its string representation +// based on its Terraform type. This is needed because the String() +// method on an attr.Value creates a 'human-readable' version of the type, which +// leads to quotes, escaped characters, and other assorted sadness. +func TFValueToString(val attr.Value) string { + if val.IsUnknown() || val.IsNull() { + return "" + } + if vs, ok := val.(interface{ ValueString() string }); ok { + return vs.ValueString() + } + if vb, ok := val.(interface{ ValueBool() bool }); ok { + return fmt.Sprintf("%t", vb.ValueBool()) + } + if vi, ok := val.(interface{ ValueInt64() int64 }); ok { + return fmt.Sprintf("%d", vi.ValueInt64()) + } + panic(fmt.Errorf("tfValueToString: value %T is not a supported type", val)) +} + +// TFListToStringSlice converts a types.List to a []string by calling +// tfValueToString on each element. +func TFListToStringSlice(l types.List) []string { + els := l.Elements() + ss := make([]string, len(els)) + for idx, el := range els { + ss[idx] = TFValueToString(el) + } + return ss +} + +// TFMapToStringMap converts a types.Map to a map[string]string by calling +// tfValueToString on each element. +func TFMapToStringMap(m types.Map) map[string]string { + els := m.Elements() + res := make(map[string]string, len(els)) + for k, v := range els { + res[k] = TFValueToString(v) + } + return res +} + +// TFLogFunc is an adapter to envbuilder/log.Func. +func TFLogFunc(ctx context.Context) log.Func { + return func(level log.Level, format string, args ...any) { + var logFn func(context.Context, string, ...map[string]interface{}) + switch level { + case log.LevelTrace: + logFn = tflog.Trace + case log.LevelDebug: + logFn = tflog.Debug + case log.LevelWarn: + logFn = tflog.Warn + case log.LevelError: + logFn = tflog.Error + default: + logFn = tflog.Info + } + logFn(ctx, fmt.Sprintf(format, args...)) + } +} + +// DockerEnv returns the keys and values of the map in the form "key=value" +// sorted by key in lexicographical order. This is the format expected by +// Docker and some other tools that consume environment variables. +func DockerEnv(m map[string]string) []string { + pairs := make([]string, 0, len(m)) + var sb strings.Builder + for k := range m { + _, _ = sb.WriteString(k) + _, _ = sb.WriteRune('=') + _, _ = sb.WriteString(m[k]) + pairs = append(pairs, sb.String()) + sb.Reset() + } + sort.Strings(pairs) + return pairs +} diff --git a/testutil/registrytest/registrytest.go b/testutil/registrytest/registrytest.go index e18043a..be8d40d 100644 --- a/testutil/registrytest/registrytest.go +++ b/testutil/registrytest/registrytest.go @@ -2,6 +2,7 @@ package registrytest import ( "fmt" + "net/http" "net/http/httptest" "net/url" "testing" @@ -13,12 +14,31 @@ import ( // New starts a new Docker registry listening on localhost. // It will automatically shut down when the test finishes. // It will store data in dir. -func New(t testing.TB, dir string) string { +func New(t testing.TB, dir string, mws ...func(http.Handler) http.Handler) string { t.Helper() regHandler := registry.New(registry.WithBlobHandler(registry.NewDiskBlobHandler(dir))) + for _, mw := range mws { + regHandler = mw(regHandler) + } regSrv := httptest.NewServer(regHandler) t.Cleanup(func() { regSrv.Close() }) regSrvURL, err := url.Parse(regSrv.URL) require.NoError(t, err) return fmt.Sprintf("localhost:%s", regSrvURL.Port()) } + +func BasicAuthMW(t testing.TB, username, password string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if username != "" || password != "" { + authUser, authPass, ok := r.BasicAuth() + if !ok || username != authUser || password != authPass { + t.Logf("basic auth failed: got user %q, pass %q", authUser, authPass) + w.WriteHeader(http.StatusUnauthorized) + return + } + } + next.ServeHTTP(w, r) + }) + } +}