Compare commits

...

37 commits

Author SHA1 Message Date
dependabot[bot]
446bafacce
chore(deps): bump actions/checkout from 4 to 5 ()
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 17:00:01 +01:00
dependabot[bot]
2025395601
chore(deps): bump actions/setup-go from 5 to 6 ()
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 16:48:11 +01:00
dependabot[bot]
30fd26107b
chore(deps): bump golang.org/x/oauth2 from 0.20.0 to 0.27.0 ()
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.20.0 to 0.27.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.20.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-version: 0.27.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-20 14:37:04 +01:00
dependabot[bot]
52535609d0
chore(deps): bump github.com/cloudflare/circl from 1.3.7 to 1.6.1 ()
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.7 to 1.6.1.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.3.7...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-13 14:22:09 +01:00
dependabot[bot]
251def2524
chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 ()
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.1 to 4.5.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.1...v4.5.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 09:18:59 +00:00
dependabot[bot]
cd9c032708
chore(deps): bump github.com/go-jose/go-jose/v4 from 4.0.1 to 4.0.5 ()
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.1 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.1...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 15:21:34 +00:00
dependabot[bot]
7b6e7d5fba
chore(deps): bump github.com/hashicorp/terraform-plugin-docs from 0.19.4 to 0.21.0 ()
chore(deps): bump github.com/hashicorp/terraform-plugin-docs

Bumps [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) from 0.19.4 to 0.21.0.
- [Release notes](https://github.com/hashicorp/terraform-plugin-docs/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-docs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-docs/compare/v0.19.4...v0.21.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-docs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 15:05:55 +00:00
dependabot[bot]
e298d87ca3
chore(deps): bump golang.org/x/net from 0.33.0 to 0.36.0 ()
* chore(deps): bump golang.org/x/net from 0.33.0 to 0.36.0

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* go mod tidy

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cian Johnston <cian@coder.com>
2025-03-13 14:30:30 +00:00
Cian Johnston
99b0d0257b
ci: update linters settings () 2025-03-13 11:15:58 +00:00
dependabot[bot]
b86f527238
chore(deps): bump golangci/golangci-lint-action from 6.2.0 to 6.3.1 ()
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.2.0 to 6.3.1.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](ec5d18412c...2e788936b0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 11:04:49 +00:00
Cian Johnston
f98d104f23
chore(README.md): update broken links () 2025-01-20 19:25:38 +02:00
dependabot[bot]
3adb53af1a
chore(deps): bump golangci/golangci-lint-action from 6.1.1 to 6.2.0 ()
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.1.1 to 6.2.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](971e284b60...ec5d18412c)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 16:58:39 +00:00
Sas Swart
82c856094e
feat: add build secrets option ()
* feat: add build secrets option

* make gen and fix tests
2025-01-16 11:39:38 +02:00
Sas Swart
dabb7f31aa
chore(deps): upgrade the envbuilder and kaniko dependencies ()
* chore(deps): upgrade the envbuilder and kaniko dependencies

* make it build
2025-01-14 17:16:24 +02:00
dependabot[bot]
bceddea8cc
chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 ()
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 14:16:08 +00:00
Cian Johnston
1a997fdd60
chore: update go-git to v5.13.1 () 2025-01-07 12:26:25 +00:00
Danielle Maywood
6795af2ba1
feat: support ENVBUILDER_GIT_SSH_PRIVATE_KEY_BASE64 () 2024-10-30 14:15:44 +00:00
dependabot[bot]
311f01b14e
chore(deps): bump crazy-max/ghaction-import-gpg from 6.1.0 to 6.2.0 ()
Bumps [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases)
- [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/v6.1.0...v6.2.0)

---
updated-dependencies:
- dependency-name: crazy-max/ghaction-import-gpg
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 12:11:31 +00:00
dependabot[bot]
e7dc9a171a
chore(deps): bump github.com/go-git/go-billy/v5 from 5.5.0 to 5.6.0 ()
Bumps [github.com/go-git/go-billy/v5](https://github.com/go-git/go-billy) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/go-git/go-billy/releases)
- [Commits](https://github.com/go-git/go-billy/compare/v5.5.0...v5.6.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-billy/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 11:58:58 +00:00
dependabot[bot]
32d72a7b4e
chore(deps): bump golangci/golangci-lint-action from 6.1.0 to 6.1.1 ()
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](aaa42aa062...971e284b60)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 18:48:36 -05:00
dependabot[bot]
875d1eb69c
chore(deps): bump github.com/coder/envbuilder from 1.0.2 to 1.0.3 ()
Bumps [github.com/coder/envbuilder](https://github.com/coder/envbuilder) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/coder/envbuilder/releases)
- [Commits](https://github.com/coder/envbuilder/compare/v1.0.2...v1.0.3)

---
updated-dependencies:
- dependency-name: github.com/coder/envbuilder
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 18:48:07 -05:00
Mathias Fredriksson
cdd57d3b36
chore(deps): update github.com/coder/envbuilder to v1.0.2 () 2024-10-02 14:45:15 +03:00
Cian Johnston
437dde9144
update envbuilder to 5c150b3c1e35 () 2024-09-27 21:21:46 +01:00
Cian Johnston
e565d7d145
fix: update envbuilder to include kaniko cache fix ()
* chore: add test for copy perms

* update kaniko and envbuilder to include fix

* add multi-stage version of copy perms test

* remove unnecessary chowns, collapse to single test

* update envbuilder and kaniko
2024-09-25 13:48:48 +01:00
dependabot[bot]
6c66d06af7
chore(deps): bump github.com/coder/serpent from 0.7.0 to 0.8.0 ()
Bumps [github.com/coder/serpent](https://github.com/coder/serpent) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/coder/serpent/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: github.com/coder/serpent
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 11:49:22 +01:00
Cian Johnston
4077a87dca
fix: set MagicDir to tempdir when performing cache probe ()
* fix: set MagicDir to tempdir when performing cache probe

* chore: update envbuilder to b7781d8

* imgutil: get default envbuilder binary path from envbuilder options
2024-09-10 09:47:39 +01:00
Cian Johnston
6137223cea
update kaniko to latest main to keep in sync with envbuilder () 2024-09-05 17:41:42 +01:00
Cian Johnston
482a446eb3
chore(internal/provider): refactor cached_image_resource ()
Addresses some non-blocking comments from :

- Extracts some of the functions in cached_image_resource.go to separate internal packages tfutil and imgutil.
- Some other functions are extracted to helpers.go.
- Extracts non-overridable flags to a package-level variable.
- Pre-allocates some slices where possible.
- Removes some unused code and renames some existing code for readability
2024-09-04 16:29:25 +01:00
Cian Johnston
23f2cf5f48
fix(internal/provider): correctly override from extra_env ()
Relates to 

Our previous logic did not pass options from extra_env to envbuilder.RunCacheProbe.
This fixes the logic and adds more comprehensive tests around the overriding logic.
Future commits will refactor this logic some more.
2024-09-04 11:19:25 +01:00
Cian Johnston
e35030b39f
chore: update envbuilder to include multi-stage cache probe fix () 2024-08-30 17:26:01 +01:00
dependabot[bot]
11c4b3b088
chore(deps): bump hashicorp/setup-terraform from 3.1.1 to 3.1.2 ()
Bumps [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/hashicorp/setup-terraform/releases)
- [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md)
- [Commits](651471c36a...b9cd54a3c3)

---
updated-dependencies:
- dependency-name: hashicorp/setup-terraform
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 12:01:54 +01:00
Cian Johnston
d6192fcd11
chore(docs): add provider schema () 2024-08-21 10:10:58 +01:00
Cian Johnston
cd1599f79d
fix(internal/provider): set all supported envbuilder options ()
* fix(internal/provider): set all supported envbuilder options

* do not allow overriding git_url and cache_repo
2024-08-16 14:55:46 +01:00
Cian Johnston
6cf3d93444
feat(internal/provider): add env_map to cached_image_resource ()
This PR adds `env_map` to `cached_image_resource.` This consists of the computed env in map format, which can be useful for other providers that do not expect `KEY=VALUE` format.
2024-08-16 09:42:27 +01:00
Cian Johnston
b55c3783a8
chore(deps): update docker/docker to v26.1.5 ()
* chore(deps): update docker/docker to v26.1.5

* also update envbuilder
2024-08-15 15:49:57 +01:00
Cian Johnston
9f455838b8
fix(provider): correctly handle devcontainer-only in cache probe mode ()
* chore(internal/provider): add test case for dockerfile-only operation

* chore(deps): update envbuilder to include 
2024-08-15 12:58:06 +01:00
Cian Johnston
68cc59d705
fix(internal/provider): correct escaping of strings in envbuilder_cached_image.env ()
Fixes 

We had previously been doing the equivalent of value.String() when writing envbuilder_cached_image.env. This was incorrectly escaping newlines, potentially breaking ENVBUILDER_INIT_SCRIPT.

This PR modifies the behaviour to correctly handle string values via ValueString() instead.
2024-08-14 14:55:22 +01:00
18 changed files with 1619 additions and 587 deletions

View file

@ -13,16 +13,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Unshallow
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
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 }}

View file

@ -22,23 +22,27 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.1.7
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: "go.mod"
cache: true
- 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
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.1.7
- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
terraform_version: "1.9.*"
terraform_wrapper: false
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: "go.mod"
cache: true
@ -65,12 +69,12 @@ jobs:
- "1.8.*"
- "1.9.*"
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.1.7
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
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

View file

@ -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
- usetesting

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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
}

112
go.mod
View file

@ -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

225
go.sum
View file

@ -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=

106
internal/imgutil/imgutil.go Normal file
View file

@ -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)
}

View file

@ -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())
}
opts.BinaryPath = envbuilderPath
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})
// 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)
}
tflog.Debug(ctx, "workspace_folder not specified, using temp dir", map[string]any{"workspace_folder": opts.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,
}
// 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
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 `<null>` 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())
}
}
return ss
}
// 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...))
}
}

View file

@ -10,105 +10,315 @@ import (
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccCachedImageResource(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*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`,
}
// 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`
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,
func TestAccCachedImageResource(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
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.
},
// 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"),
),
},
// Should produce an empty plan after apply
{
Config: deps.Config(t),
PlanOnly: true,
},
// 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_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 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",
),
)
},
},
{
// 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",
},
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...)
}

View file

@ -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
}

View file

@ -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.`,
}
}

View file

@ -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)
}

View file

@ -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
}
}

92
internal/tfutil/tfutil.go Normal file
View file

@ -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
}

View file

@ -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)
})
}
}