Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ terrapod-migrate rewrite --state-file migration-state.json --source-dir ~/code/a
against each repo (locally cloned, on the operator's own machine). The
tool walks the directory tree and mechanically rewrites:

- `terraform { cloud { hostname = "app.terraform.io", organization = "acme" ... } }` blocks → Terrapod hostname + `"default"` organization. Both `workspaces { name = "..." }` and `workspaces { tags = [...] }` forms are supported — only `hostname` and `organization` change; the workspace selection inside stays as-is (Terrapod's `tfe_v2` endpoint accepts the same `tags = [...]` syntax and translates internally). Tags are matched against workspace **labels**: a bare tag (`"core"`) matches any workspace with that label key, and a `key:value` tag (`"repo:tf-aws-core"`) matches that exact label. Use the colon form to select by key+value — OpenTofu rejects the Terraform 1.10+ map form (`tags = { repo = "..." }`), so `tags = ["repo:tf-aws-core"]` is the portable equivalent.
- `terraform { cloud { hostname = "app.terraform.io", organization = "acme" ... } }` blocks → Terrapod hostname + `"default"` organization. Both `workspaces { name = "..." }` and `workspaces { tags = [...] }` forms are supported — only `hostname` and `organization` change; the workspace selection inside stays as-is (Terrapod's `tfe_v2` endpoint accepts the same `tags = [...]` syntax and translates internally). Tags are matched against workspace **labels**: a bare tag (`"core"`) matches any workspace with that label key, and a `key:value` tag (`"repo:web-app"`) matches that exact label. Use the colon form to select by key+value — OpenTofu rejects the Terraform 1.10+ map form (`tags = { repo = "..." }`), so `tags = ["repo:web-app"]` is the portable equivalent.
- `terraform { backend "remote" { hostname = "app.terraform.io", organization = "acme" ... } }` blocks → same destination as `cloud {}`.
- `source = "app.terraform.io/acme/<module>"` private-module references → `"<terrapod-host>/default/<module>"`.

Expand Down
2 changes: 1 addition & 1 deletion services/terrapod/api/routers/tfe_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ def _parse_tag_filters(request: Request) -> list[tuple[str, str | None]]:
# List form: search[tags]=a,b,c=d,e:f
# A token is a bare `key`, or `key=value` / `key:value`. tofu's cloud
# block emits the COLON form for set-of-string tags
# (`tags = ["repo:tf-aws-core"]`) because `=` is not a legal tofu/TFC
# (`tags = ["repo:web-app"]`) because `=` is not a legal tofu/TFC
# tag character and the map form isn't supported in OpenTofu; the `=`
# form comes from go-tfe / direct API callers. Split on whichever
# separator appears first so both map to an exact key=value label.
Expand Down
10 changes: 5 additions & 5 deletions services/tests/api/test_workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,9 +695,9 @@ def test_list_form_colon_key_value(self):
from terrapod.api.routers.tfe_v2 import _parse_tag_filters

# OpenTofu's cloud block emits `key:value` for set-of-string tags
# (`tags = ["repo:tf-aws-core"]`) — map tags aren't supported there.
out = _parse_tag_filters(self._req("search%5Btags%5D=repo:tf-aws-core"))
assert out == [("repo", "tf-aws-core")]
# (`tags = ["repo:web-app"]`) — map tags aren't supported there.
out = _parse_tag_filters(self._req("search%5Btags%5D=repo:web-app"))
assert out == [("repo", "web-app")]

def test_list_form_colon_value_with_hyphens(self):
from terrapod.api.routers.tfe_v2 import _parse_tag_filters
Expand All @@ -710,8 +710,8 @@ def test_list_form_mixed_colon_equals_and_bare(self):
from terrapod.api.routers.tfe_v2 import _parse_tag_filters

# Colon, equals, and bare tokens coexist; earliest separator wins.
out = _parse_tag_filters(self._req("search%5Btags%5D=core,repo:tf-aws-core,env=prod"))
assert out == [("core", None), ("repo", "tf-aws-core"), ("env", "prod")]
out = _parse_tag_filters(self._req("search%5Btags%5D=core,repo:web-app,env=prod"))
assert out == [("core", None), ("repo", "web-app"), ("env", "prod")]

def test_list_form_colon_empty_key_skipped(self):
from terrapod.api.routers.tfe_v2 import _parse_tag_filters
Expand Down
Loading