Compare commits

...

43 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
Cian Johnston
b35004a702
feat: add warning diags to aid troubleshooting ()
Adds warning diagnostics to aid in troubleshooting issues with cached builds.
2024-08-13 13:13:45 +01:00
dependabot[bot]
9a4bf0993d
chore(deps): bump github.com/hashicorp/terraform-plugin-testing from 1.9.0 to 1.10.0 ()
chore(deps): bump github.com/hashicorp/terraform-plugin-testing

Bumps [github.com/hashicorp/terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/hashicorp/terraform-plugin-testing/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-testing/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-testing/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-testing
  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-08-12 12:17:17 +01:00
Cian Johnston
f8a49e84ff
ci: push envbuilder image to local registry () 2024-08-12 11:51:06 +01:00
Mathias Fredriksson
a2a5fcf79b
fix: prevent test flake by waiting on copy () 2024-08-12 12:54:30 +03:00
Cian Johnston
a4e82b3d8c
readme: remove duplicated usage note 2024-08-09 15:47:15 +01:00
Cian Johnston
37ddd681c7
add note regarding platform compatibility 2024-08-09 15:46:29 +01:00
20 changed files with 1660 additions and 602 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

@ -41,15 +41,20 @@ test-registry-container: .registry-cache
# Pulls images referenced in integration tests and pushes them to the local cache.
.PHONY: test-images-push
test-images-push: .registry-cache/docker/registry/v2/repositories/test-ubuntu
test-images-push: .registry-cache/docker/registry/v2/repositories/test-ubuntu .registry-cache/docker/registry/v2/repositories/envbuilder
.PHONY: test-images-pull
test-images-pull:
docker pull ubuntu:latest
docker tag ubuntu:latest localhost:5000/test-ubuntu:latest
docker pull ghcr.io/coder/envbuilder-preview:latest
docker tag ghcr.io/coder/envbuilder-preview:latest localhost:5000/envbuilder:latest
.registry-cache:
mkdir -p .registry-cache && chmod -R ag+w .registry-cache
.registry-cache/docker/registry/v2/repositories/test-ubuntu:
docker push localhost:5000/test-ubuntu:latest
.registry-cache/docker/registry/v2/repositories/envbuilder:
docker push localhost:5000/envbuilder:latest

View file

@ -5,15 +5,17 @@ The `terraform-provider-envbuilder` is a Terraform provider that acts as a helpe
It is used to determine if a pre-built image of a dev container built from a given Git repository is present in a given Docker registry.
If it is found that building a particular dev container would produce the same image that is already present in the remote registry, then that image can be used to start the container instead, skipping over the build phase.
> **Note:** currently, this provider can only be run on Linux platforms. We are [investigating support](https://github.com/coder/terraform-provider-envbuilder/issues/26) for other platforms.
## Usage
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
@ -30,10 +32,6 @@ For use with [Coder](https://github.com/coder/coder), see the [Dev Containers do
go install
```
## Using the provider
Fill this in for each provider
## Developing the Provider
If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (see [Requirements](#requirements) above).

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
}

115
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.9.0
github.com/stretchr/testify v1.9.0
github.com/hashicorp/terraform-plugin-testing v1.10.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
@ -158,16 +161,17 @@ require (
github.com/hashicorp/go-memdb v1.3.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
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.7.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
@ -181,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
@ -203,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
@ -221,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
@ -236,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
@ -247,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
@ -260,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
@ -270,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.14.4 // 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
@ -289,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.25.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.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

231
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=
@ -441,6 +444,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@ -452,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.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk=
github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA=
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=
@ -474,8 +479,8 @@ github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9T
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
github.com/hashicorp/terraform-plugin-testing v1.9.0 h1:xOsQRqqlHKXpFq6etTxih3ubdK3HVDtfE1IY7Rpd37o=
github.com/hashicorp/terraform-plugin-testing v1.9.0/go.mod h1:fhhVx/8+XNJZTD5o3b4stfZ6+q7z9+lIWigIYdT6/44=
github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
@ -516,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=
@ -548,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=
@ -587,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=
@ -600,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=
@ -626,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=
@ -686,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=
@ -703,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=
@ -716,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=
@ -744,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=
@ -764,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=
@ -803,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.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
github.com/zclconf/go-cty v1.14.4/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=
@ -874,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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
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=
@ -887,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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.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=
@ -908,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=
@ -922,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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.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=
@ -955,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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.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.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
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=
@ -981,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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
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=
@ -998,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,24 +317,48 @@ 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) {
tflog.Debug(ctx, "Image previously not found. Recreating.", map[string]any{"ref": data.Image.ValueString()})
resp.Diagnostics.AddWarning(
"Re-running cache probe due to previous miss.",
fmt.Sprintf(`The previous state specifies image == builder_image %q, which indicates a previous cache miss.`,
data.Image.ValueString(),
))
resp.State.RemoveResource(ctx)
return
}
// 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") {
resp.Diagnostics.AddError("Error checking remote image", err.Error())
// Explicitly not making this an error diag.
resp.Diagnostics.AddWarning("Unable to check remote image.",
fmt.Sprintf("The repository %q returned the following error while checking for a cached image %q: %q",
data.CacheRepo.ValueString(),
data.Image.ValueString(),
err.Error(),
))
return
}
// Image does not exist any longer! Remove the resource so we can re-create
// it next time.
tflog.Debug(ctx, "Remote image does not exist any longer. Recreating.", map[string]any{"ref": data.Image.ValueString()})
resp.Diagnostics.AddWarning("Previously built image not found, recreating.",
fmt.Sprintf("The repository %q does not contain the cached image %q. It will be rebuilt in the next apply.",
data.CacheRepo.ValueString(),
data.Image.ValueString(),
))
resp.State.RemoveResource(ctx)
return
}
@ -322,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)...)
}
@ -358,14 +387,29 @@ 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 {
// FIXME: there are legit errors that can crop up here.
// We should add a sentinel error in Kaniko for uncached layers, and check
// it here.
tflog.Info(ctx, "cached image not found", map[string]any{"err": err.Error()})
resp.Diagnostics.AddWarning("Cached image not found.", fmt.Sprintf(
"Failed to find cached image in repository %q. It will be rebuilt in the next apply. Error: %s",
data.CacheRepo.ValueString(),
err.Error(),
))
data.Image = data.BuilderImage
} else if digest, err := cachedImg.Digest(); err != nil {
// There's something seriously up with this image!
@ -376,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)...)
@ -433,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())
@ -445,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)
@ -457,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 TestAccCachedImageDataSource(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

@ -135,17 +135,19 @@ func startSSHServer(ctx context.Context, t testing.TB) string {
_, _ = io.Copy(in, s)
_ = in.Close()
}()
outCopyDone := make(chan struct{})
go func() {
_, _ = io.Copy(s, out)
_ = out.Close()
_ = s.CloseWrite()
close(outCopyDone)
}()
err = cmd.Wait()
if err != nil {
t.Logf("command failed: %s", err)
}
<-outCopyDone
t.Logf("session ended: %s", s.RawCommand())
t.Logf("session ended (cmd=%q, code=%d)", s.RawCommand(), cmd.ProcessState.ExitCode())
err = s.Exit(cmd.ProcessState.ExitCode())
if err != nil {

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", "ghcr.io/coder/envbuilder-preview")
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)
})
}
}