Refactor Terraform and Hex handlers to support multiple registries per organization#77
Refactor Terraform and Hex handlers to support multiple registries per organization#77AbhishekBhaskar wants to merge 4 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors the Terraform registry and Hex organization handlers to store credentials in slices rather than maps, enabling multiple credentials to coexist (e.g., multiple registries on the same hostname) and aligning matching behavior more closely with other request handlers in the proxy.
Changes:
- Terraform handler: replace host→token map with a slice of
{host,url,token}entries and match requests by URL prefix first, then host. - Hex handler: replace org→token map with a slice of
{organization,token}entries and match by iterating credentials.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| internal/handlers/terraform_registry.go | Stores static credentials as a slice and introduces URL-prefix matching to support multiple registries on the same host. |
| internal/handlers/hex_organization.go | Stores org credentials as a slice and updates request matching to iterate credentials. |
Comments suppressed due to low confidence (2)
internal/handlers/terraform_registry.go:54
- NewTerraformRegistryHandler appends static credentials even when
tokenis empty (or when bothhostandurlare empty). If a matching request occurs, this will setAuthorization: Bearerand may overwrite an existing Authorization header with an invalid value. Consider filtering out incomplete/invalid static credentials during construction (e.g., require a non-empty token and at least one of url/host).
terraformCred := terraformRegistryCredentials{
host: host,
url: credential.GetString("url"),
token: credential.GetString("token"),
}
handler.credentials = append(handler.credentials, terraformCred)
internal/handlers/terraform_registry.go:84
- This new URL-first matching logic is intended to support multiple Terraform registries that share a hostname but differ by path/prefix, but there’s no coverage here for the ambiguous/overlap case (e.g., two credentials for
terraform.example.com/org1andterraform.example.com/org2and ensuring the correct token is chosen for each path). Adding a test for multiple credentials on the same host with different URL prefixes would help prevent regressions.
for _, cred := range h.credentials {
// Match by URL first (more specific), then by host
if cred.url != "" {
if helpers.UrlMatchesRequest(request, cred.url, true) {
logging.RequestLogf(context, "* authenticating terraform registry request (url: %s)", cred.url)
request.Header.Set("Authorization", "Bearer "+cred.token)
return request, nil
}
} else if cred.host != "" {
if helpers.CheckHost(request, cred.host) {
logging.RequestLogf(context, "* authenticating terraform registry request (host: %s)", cred.host)
request.Header.Set("Authorization", "Bearer "+cred.token)
return request, nil
}
}
|
|
@kbukum1 I've addressed the above comment with the following changes:
|
kbukum1
left a comment
There was a problem hiding this comment.
I'm approving the changes. @jakecoffman, @JamieMagee — it would be great to get your confirmation that these structural changes look good.
During our investigation, we noticed Terraform and Hex had a different data structure compared to the other ecosystems. @AbhishekBhaskar aligned them with the existing structure, but we'd love to know if there was a specific reason they differed. Please let us know if you see any issues.
jakecoffman
left a comment
There was a problem hiding this comment.
I don't know the history of why these endpoints are this way. You might check the private repo's history to see if there was a reason. The Terraform change is probably ok, but the hex change seems like it wouldn't change behavior.
|
@AbhishekBhaskar , we may need to discuss the approach. I am currently creating an approach and I am planing to apply to all. Not sure but we may want to either ships yours and create another to match the sollution I am creating or fix it in one PR. You can check followings to see what I am trying to do |
There was a problem hiding this comment.
Pull request overview
Refactors Terraform registry and Hex organization handlers to store credentials in slices (instead of maps), enabling multiple credentials per host/organization and more consistent matching behavior with other request handlers in the proxy.
Changes:
- Terraform: replace host→token map with a credential slice, add URL-prefix matching with path-boundary checks, and sort credentials for specificity.
- Terraform: add tests covering multiple URL-path credentials on the same host and path-boundary behavior.
- Hex: replace org→token map with a credential slice, support
keywith legacytokenfallback, and add backwards-compatibility tests.
Show a summary per file
| File | Description |
|---|---|
| internal/handlers/terraform_registry.go | Switches to slice-based credential storage; adds URL boundary matching and sorts credentials for more specific matches. |
| internal/handlers/terraform_registry_test.go | Adds coverage for multiple registry URLs on the same host and path-boundary correctness. |
| internal/handlers/hex_organization.go | Switches to slice-based storage; supports key and legacy token; updates matching to iterate credentials. |
| internal/handlers/hex_organization_test.go | Updates tests to use key and adds backwards compatibility coverage for legacy token. |
Copilot's findings
- Files reviewed: 4/4 changed files
- Comments generated: 3
| reqOrg := pathParts[1] | ||
| for _, cred := range h.credentials { | ||
| if cred.organization == reqOrg { | ||
| logging.RequestLogf(ctx, "* authenticating hex request (org: %s)", reqOrg) | ||
| req.Header.Set("authorization", cred.key) | ||
| return req, nil | ||
| } |
There was a problem hiding this comment.
Behavior change vs the previous map implementation: with a slice, the first matching credential wins. Previously, the last configured credential for an organization would overwrite earlier ones in the map. To preserve backwards-compatible precedence, consider iterating credentials in reverse order during matching or de-duplicating by organization during handler construction (keeping the last).
| type hexOrganizationCredentials struct { | ||
| organization string | ||
| key string | ||
| } |
There was a problem hiding this comment.
PR description mentions a token field, but the refactor introduces a key field and also accepts config key/legacy token. Either update the PR description to match the implementation or rename fields for consistency to avoid confusion for reviewers and future maintainers.
| // Sort credentials by URL path length descending (longest first) | ||
| sort.Slice(handler.credentials, func(i, j int) bool { | ||
| return len(handler.credentials[i].url) > len(handler.credentials[j].url) | ||
| }) |
There was a problem hiding this comment.
The comment says "URL path length" but the code sorts by len(cred.url) (full URL string length), and sort.Slice is not stable when lengths are equal. This can lead to surprising/unstable credential precedence (e.g., two equivalent URLs differing only by trailing /, or multiple host-only creds where url == "" for all entries). Consider normalizing stored URLs/paths (e.g., trimming trailing /) and sorting by normalized parsed path length with a deterministic tie-breaker (or using sort.SliceStable).
Refactors
terraform_registry.goandhex_organization.gofrom map-based to slice-based credential storage, enabling support for multiple private registries per ecosystem and aligning with other handler implementations.Problem:
The Terraform handler used
map[string]stringkeyed by host, which only allowed ONE credential per host. If two Terraform registries shared the same host (e.g.,terraform.example.com/org1andterraform.example.com/org2), the second credential would overwrite the first.Changes:
terraform_registry.gocredentials map[string]string→credentials []terraformRegistryCredentialsterraformRegistryCredentialsstruct withhost,url,tokenfieldsHandleRequestto iterate credentials with URL-first matching viahelpers.UrlMatchesRequest(), then host-based matching viahelpers.CheckHost()hex_organization.goorgTokens map[string]string→credentials []hexOrganizationCredentialshexOrganizationCredentialsstruct withorganization,tokenfields