Skip to content

Commit d6be488

Browse files
rrajagopalan-equinixCopilotctreatma
authored
feat: Workload identity federation support for Fabric using STS (#908)
This pull request introduces support for Workload Identity Federation (WIF) using Equinix STS, along with updates to documentation, provider configuration, and acceptance testing. The changes enable authentication via OIDC tokens, enhance the Equinix Terraform provider, and improve testing capabilities. ### Workload Identity Federation (WIF) Support: * [`docs/guides/sts_wif_setup.md`](diffhunk://#diff-3863d4c3c06fdf2fd239a5ce5ecec057e3fb08e8d6a1036537fe4a344a72fafdR1-R113): Added a comprehensive guide for setting up Workload Identity Federation using Equinix STS, including steps for obtaining authentication tokens, establishing trust with an identity provider, and authorizing workloads. * [`docs/index.md`](diffhunk://#diff-b4d68dc855d0f9476d3f2ee343853bd21bf82ea9960d0cf06661baa244439dd6R11-R12): Updated documentation to include details about using Workload Identity Tokens for authentication with Equinix Fabric, including alpha feature limitations and configuration examples. [[1]](diffhunk://#diff-b4d68dc855d0f9476d3f2ee343853bd21bf82ea9960d0cf06661baa244439dd6R11-R12) [[2]](diffhunk://#diff-b4d68dc855d0f9476d3f2ee343853bd21bf82ea9960d0cf06661baa244439dd6R49-R61) [[3]](diffhunk://#diff-b4d68dc855d0f9476d3f2ee343853bd21bf82ea9960d0cf06661baa244439dd6R103-R105) * [`examples/example_4.tf`](diffhunk://#diff-d7c9284d94752cdab597259337b94de85088016938d348fb88450621b2c6112dR1-R8): Added an example Terraform configuration demonstrating the use of Workload Identity Federation with `sts_auth_scope` and `sts_source_token`. ### Provider Enhancements: * [`equinix/provider.go`](diffhunk://#diff-17a1d27f648a15532c90c0ed8e7b143adde23b59dec5bbe4222db41a0a94ea0aR86-R104): Added new provider arguments (`sts_auth_scope`, `sts_endpoint`, `sts_source_token`) for configuring STS authentication. Updated the `configureProvider` function to support STS-based authentication. [[1]](diffhunk://#diff-17a1d27f648a15532c90c0ed8e7b143adde23b59dec5bbe4222db41a0a94ea0aR86-R104) [[2]](diffhunk://#diff-17a1d27f648a15532c90c0ed8e7b143adde23b59dec5bbe4222db41a0a94ea0aR140-R142) * [`internal/config/config.go`](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdR20): Introduced STS-related environment variables and default values. Enhanced the `Config` struct and authentication logic to support STS tokens. Refactored client creation methods for improved modularity. [[1]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdR20) [[2]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdR39-R41) [[3]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdR78-R80) [[4]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdR100-R112) [[5]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdL110-R139) [[6]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdR197-R205) [[7]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdL177-R219) [[8]](diffhunk://#diff-54c7c1af5fa8d5db4dc49f0e8e80e93ba2b1183ba4d5c9e2e5729e6deae6a3cdL187-R232) ### Acceptance Testing Improvements: * [`internal/acceptance/acceptance.go`](diffhunk://#diff-a7084a87154a2249e8cc364137ada5573520dc434b8138618a749ac4f41d545eR1-R4): Updated acceptance testing utilities to include checks for STS authentication credentials. Enhanced pre-check methods to validate STS setup alongside existing authentication mechanisms. [[1]](diffhunk://#diff-a7084a87154a2249e8cc364137ada5573520dc434b8138618a749ac4f41d545eR1-R4) [[2]](diffhunk://#diff-a7084a87154a2249e8cc364137ada5573520dc434b8138618a749ac4f41d545eR27-R33) [[3]](diffhunk://#diff-a7084a87154a2249e8cc364137ada5573520dc434b8138618a749ac4f41d545eR57-R59) [[4]](diffhunk://#diff-a7084a87154a2249e8cc364137ada5573520dc434b8138618a749ac4f41d545eR68-R101) ### Dependency Updates: * [`go.mod`](diffhunk://#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6L6-L8): Upgraded `github.com/equinix/equinix-sdk-go` to version `v0.54.0` to support new STS features. Removed unused dependencies. ### Documentation Improvements: * [`internal/provider/provider.go`](diffhunk://#diff-58d6a027753b50994deb7e11e4a99dde423f35844986019bd9cea5e0c94aba22R1-R2): Added package-level documentation for the Terraform provider implementation. --------- Signed-off-by: rrajagopalan-equinix <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Charles Treatman <[email protected]>
1 parent dfd8929 commit d6be488

File tree

13 files changed

+618
-79
lines changed

13 files changed

+618
-79
lines changed

.github/workflows/fabric_acctest.yml

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ on:
2626
workflow_dispatch:
2727

2828
permissions:
29+
#actions: write required to update OIDC subject claim template
2930
pull-requests: read
3031
contents: read
32+
id-token: write
3133

3234
jobs:
3335

@@ -44,6 +46,8 @@ jobs:
4446
steps:
4547
- run: true
4648

49+
# The GitHub OIDC subject claim template has been customized at the repository level to include only the workflow name. Long-term, we should consider using the default template for tests.
50+
4751
build:
4852
name: Build
4953
needs: authorize
@@ -85,7 +89,6 @@ jobs:
8589
terraform:
8690
- '1.5'
8791
steps:
88-
8992
- name: Check out code into the Go module directory
9093
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
9194
with:
@@ -161,7 +164,6 @@ jobs:
161164
terraform:
162165
- '1.5'
163166
steps:
164-
165167
- name: Check out code into the Go module directory
166168
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
167169
with:
@@ -223,6 +225,81 @@ jobs:
223225
token: ${{ secrets.CODECOV_TOKEN }}
224226
files: ./coverage_pfcr.txt
225227

228+
test-STS-creds:
229+
name: Matrix Test
230+
needs: build
231+
runs-on: ubuntu-latest
232+
env:
233+
EQUINIX_API_ENDPOINT: "https://uatapi.equinix.com"
234+
EQUINIX_STS_ENDPOINT: "https://sts.uat.equinix.com"
235+
timeout-minutes: 240
236+
strategy:
237+
fail-fast: false
238+
matrix:
239+
version:
240+
- stable
241+
terraform:
242+
- '1.5'
243+
sts_config:
244+
- name: "default"
245+
env_var_name: "EQUINIX_TOKEN_EXCHANGE_SUBJECT_TOKEN"
246+
set_custom_env_var: false
247+
token_exchange_subject_token_env_var: null
248+
- name: "custom"
249+
env_var_name: "CUSTOM_STS_TOKEN"
250+
set_custom_env_var: true
251+
token_exchange_subject_token_env_var: "CUSTOM_STS_TOKEN"
252+
steps:
253+
- id: get_id_token
254+
name: Get GitHub OIDC Token for PFCR
255+
uses: actions/github-script@v6
256+
with:
257+
script: |
258+
try {
259+
const idToken = await core.getIDToken('gha-fcr-client');
260+
console.log('Token generated with audience: gha-fcr-client');
261+
core.setOutput('id_token', idToken);
262+
} catch (error) {
263+
console.error('Error getting OIDC token:', error.message);
264+
core.setFailed(`Error getting OIDC token: ${error.message}`);
265+
}
266+
result-encoding: string
267+
268+
- name: Check out code into the Go module directory
269+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
270+
with:
271+
ref: ${{ github.event.pull_request.head.sha || github.ref }}
272+
273+
- name: Set up Go
274+
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
275+
with:
276+
go-version-file: './go.mod'
277+
id: go
278+
279+
- name: Get dependencies
280+
run: |
281+
go mod download
282+
283+
- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3
284+
with:
285+
terraform_version: ${{ matrix.terraform }}
286+
terraform_wrapper: false
287+
288+
- name: TF Fabric PFCR acceptance tests STS creds
289+
timeout-minutes: 180
290+
env:
291+
TF_ACC: "1"
292+
TF_ACC_FABRIC_CONNECTIONS_TEST_DATA: ${{ secrets.TF_ACC_FABRIC_CONNECTIONS_TEST_DATA }}
293+
TF_ACC_FABRIC_DEDICATED_PORTS: ${{ secrets.TF_ACC_FABRIC_DEDICATED_PORTS }}
294+
TF_ACC_FABRIC_MARKET_PLACE_SUBSCRIPTION_ID: ${{ secrets.TF_ACC_FABRIC_MARKET_PLACE_SUBSCRIPTION_ID }}
295+
TF_ACC_FABRIC_STREAM_TEST_DATA: ${{ secrets.TF_ACC_FABRIC_STREAM_TEST_DATA }}
296+
EQUINIX_TOKEN_EXCHANGE_SCOPE: ${{ secrets.EQUINIX_STS_AUTH_SCOPE_PFCR }}
297+
METAL_AUTH_TOKEN: ${{ secrets.METAL_AUTH_TOKEN }}
298+
${{ matrix.sts_config.env_var_name }}: ${{ steps.get_id_token.outputs.id_token }}
299+
${{ matrix.sts_config.set_custom_env_var && 'EQUINIX_TOKEN_EXCHANGE_SUBJECT_TOKEN_ENV_VAR' || 'SKIP' }}: ${{ matrix.sts_config.token_exchange_subject_token_env_var || '' }}
300+
run: |
301+
go test ./... --run "(TestAccFabricCreatePort2SPConnection_PFCR|TestAccCloudRouterCreateOnlyRequiredParameters_PFCR)" -v -coverprofile coverage_pfcr.txt -covermode=atomic -count 1
302+
226303
upload-test-report:
227304
name: Upload Testing Report
228305
if: always()

docs/index.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ The Equinix provider is used to interact with the resources provided by Equinix
88

99
For information about obtaining API key and secret required for Equinix Fabric and Network Edge refer to [Generating Client ID and Client Secret key](https://developer.equinix.com/dev-docs/fabric/getting-started/getting-access-token#generating-client-id-and-client-secret) from [Equinix Developer Platform portal](https://developer.equinix.com).
1010

11+
Equinix Fabric also supports authentication using a [Workload Identity Token](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/workload-identity-tokens), which can be used in place of the `client_id` and `client_secret` arguments. Requires an authorization scope and OIDC token from an IdP trusted by Equinix STS. Please note that this is an alpha feature not available for all users. Using workload identity tokens will override client ID/secret, you must use [provider aliases](https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations) to manage both workload identity tokens and client ID/secret in a single Terraform configuration.
12+
1113
Interacting with Equinix Metal requires an API auth token that can be generated at [Project-level](https://metal.equinix.com/developers/docs/accounts/projects/#api-keys) or [User-level](https://metal.equinix.com/developers/docs/accounts/users/#api-keys) tokens can be used.
1214

1315
If you are only using Equinix Metal resources, you may omit the Client ID and Client Secret provider configuration parameters needed to access other Equinix resource types (Network Edge, Fabric, etc).
@@ -44,6 +46,20 @@ provider "equinix" {
4446
}
4547
```
4648

49+
Workload Identity Tokens can be used in service authorization scenarios, like HCP Terraform. Other credential variables are optional for `equinix_fabric_*` resources and datasources when using this method.
50+
51+
```terraform
52+
# Configuration for using Workload Identity Federation
53+
provider "equinix" {
54+
# Desired scope of the requested security token. Must be an Access Policy ERN or a string of the form `roleassignments:<organization_id>`
55+
token_exchange_scope = "roleassignments:<organization_id>"
56+
57+
# The name of the environment variable containing the token exchange subject token
58+
# For example, HCP Terraform automatically sets TFC_WORKLOAD_IDENTITY_TOKEN
59+
token_exchange_subject_token_env_var = "TFC_WORKLOAD_IDENTITY_TOKEN"
60+
}
61+
```
62+
4763
Example provider configuration using `environment variables`:
4864

4965
```sh
@@ -85,4 +101,8 @@ These parameters can be provided in [Terraform variable files](https://www.terra
85101
- `max_retry_wait_seconds` (Number) Maximum number of seconds to wait before retrying a request.
86102
- `request_timeout` (Number) The duration of time, in seconds, that the Equinix Platform API Client should wait before canceling an API request. Canceled requests may still result in provisioned resources. (Defaults to `30`)
87103
- `response_max_page_size` (Number) The maximum number of records in a single response for REST queries that produce paginated responses. (Default is client specific)
104+
- `sts_endpoint` (String) The STS API base URL to point to the desired environment. This argument can also be specified with the `EQUINIX_STS_ENDPOINT` shell environment variable. (Defaults to `https://sts.eqix.equinix.com`). Please note that STS is an alpha feature and not available for all users.
88105
- `token` (String) API tokens are generated from API Consumer clients using the [OAuth2 API](https://developer.equinix.com/dev-docs/fabric/getting-started/getting-access-token#request-access-and-refresh-tokens). This argument can also be specified with the `EQUINIX_API_TOKEN` shell environment variable.
106+
- `token_exchange_scope` (String) The scope of the authentication token. Must be an access policy ERN or a string of the form `roleassignments:<org_id>`. This argument can also be specified with the `EQUINIX_TOKEN_EXCHANGE_SCOPE` shell environment variable. Please note that token exchange is an alpha feature and not available for all users.
107+
- `token_exchange_subject_token` (String) The subject token to use for token exchange authentication. Must be an OIDC ID token issued by an OIDC provider trusted by Equinix STS. If not set, the provider will use the environment variable specified in `token_exchange_subject_token_env_var`. Please note that token exchange is an alpha feature and not available for all users.
108+
- `token_exchange_subject_token_env_var` (String) The name of the environment variable containing the subject token for token exchange. This argument can also be specified with the `EQUINIX_TOKEN_EXCHANGE_SUBJECT_TOKEN_ENV_VAR` shell environment variable. (Defaults to `EQUINIX_TOKEN_EXCHANGE_SUBJECT_TOKEN`). Please note that token exchange is an alpha feature and not available for all users.

equinix/provider.go

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,31 @@ func Provider() *schema.Provider {
8383
Default: 30,
8484
Description: "Maximum number of seconds to wait before retrying a request.",
8585
},
86+
"token_exchange_scope": {
87+
Type: schema.TypeString,
88+
Optional: true,
89+
DefaultFunc: schema.EnvDefaultFunc(config.TokenExchangeScopeEnvVar, ""),
90+
Description: "The scope of the authentication token. Must be an access policy ERN or a string of the form `roleassignments:<org_id>`. This argument can also be specified with the `EQUINIX_TOKEN_EXCHANGE_SCOPE` shell environment variable. Please note that token exchange is an alpha feature and not available for all users.",
91+
},
92+
"sts_endpoint": {
93+
Type: schema.TypeString,
94+
Optional: true,
95+
DefaultFunc: schema.EnvDefaultFunc(config.StsEndpointEnvVar, config.DefaultStsBaseURL),
96+
ValidateFunc: validation.IsURLWithHTTPorHTTPS,
97+
Description: fmt.Sprintf("The STS API base URL to point to the desired environment. This argument can also be specified with the `EQUINIX_STS_ENDPOINT` shell environment variable. (Defaults to `%s`). Please note that STS is an alpha feature and not available for all users.", config.DefaultStsBaseURL),
98+
},
99+
"token_exchange_subject_token": {
100+
Type: schema.TypeString,
101+
Optional: true,
102+
Description: "The subject token to use for token exchange authentication. Must be an OIDC ID token issued by an OIDC provider trusted by Equinix STS. If not set, the provider will use the environment variable specified in `token_exchange_subject_token_env_var`. Please note that token exchange is an alpha feature and not available for all users.",
103+
},
104+
105+
"token_exchange_subject_token_env_var": {
106+
Type: schema.TypeString,
107+
Optional: true,
108+
DefaultFunc: schema.EnvDefaultFunc(config.TokenExchangeSubjectTokenEnvVarEnvVar, config.DefaultTokenExchangeSubjectTokenEnvVar),
109+
Description: fmt.Sprintf("The name of the environment variable containing the subject token for token exchange. This argument can also be specified with the `EQUINIX_TOKEN_EXCHANGE_SUBJECT_TOKEN_ENV_VAR` shell environment variable. (Defaults to `%s`). Please note that token exchange is an alpha feature and not available for all users.", config.DefaultTokenExchangeSubjectTokenEnvVar),
110+
},
86111
},
87112
DataSourcesMap: datasources,
88113
ResourcesMap: resources,
@@ -109,15 +134,19 @@ func configureProvider(ctx context.Context, d *schema.ResourceData, p *schema.Pr
109134
rt := d.Get("request_timeout").(int)
110135

111136
config := config.Config{
112-
AuthToken: d.Get("auth_token").(string),
113-
BaseURL: d.Get("endpoint").(string),
114-
ClientID: d.Get("client_id").(string),
115-
ClientSecret: d.Get("client_secret").(string),
116-
Token: d.Get("token").(string),
117-
RequestTimeout: time.Duration(rt) * time.Second,
118-
PageSize: d.Get("response_max_page_size").(int),
119-
MaxRetries: d.Get("max_retries").(int),
120-
MaxRetryWait: time.Duration(mrws) * time.Second,
137+
AuthToken: d.Get("auth_token").(string),
138+
BaseURL: d.Get("endpoint").(string),
139+
ClientID: d.Get("client_id").(string),
140+
ClientSecret: d.Get("client_secret").(string),
141+
Token: d.Get("token").(string),
142+
RequestTimeout: time.Duration(rt) * time.Second,
143+
PageSize: d.Get("response_max_page_size").(int),
144+
MaxRetries: d.Get("max_retries").(int),
145+
MaxRetryWait: time.Duration(mrws) * time.Second,
146+
TokenExchangeScope: d.Get("token_exchange_scope").(string),
147+
StsBaseURL: d.Get("sts_endpoint").(string),
148+
TokenExchangeSubjectToken: d.Get("token_exchange_subject_token").(string),
149+
TokenExchangeSubjectTokenEnvVar: d.Get("token_exchange_subject_token_env_var").(string),
121150
}
122151
meta := providerMeta{}
123152

examples/example_4.tf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Configuration for using Workload Identity Federation
2+
provider "equinix" {
3+
# Desired scope of the requested security token. Must be an Access Policy ERN or a string of the form `roleassignments:<organization_id>`
4+
token_exchange_scope = "roleassignments:<organization_id>"
5+
6+
# The name of the environment variable containing the token exchange subject token
7+
# For example, HCP Terraform automatically sets TFC_WORKLOAD_IDENTITY_TOKEN
8+
token_exchange_subject_token_env_var = "TFC_WORKLOAD_IDENTITY_TOKEN"
9+
}

internal/acceptance/acceptance.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Package acceptance provides Utilities and test framework setup for running
2+
// acceptance tests for the Equinix Terraform provider. It handles provider
3+
// configuration, authentication verification, and prerequisite checks for
4+
// testing against Equinix Fabric, Network Edge, and Metal services.
15
package acceptance
26

37
import (
@@ -20,9 +24,13 @@ const (
2024
)
2125

2226
var (
23-
TestAccProvider *schema.Provider
24-
TestAccProviders map[string]*schema.Provider
25-
TestExternalProviders map[string]resource.ExternalProvider
27+
// TestAccProvider is the Equinix provider instance used for acceptance testing
28+
TestAccProvider *schema.Provider
29+
// TestAccProviders is a map of provider names to their schema.Provider instances used in acceptance tests.
30+
TestAccProviders map[string]*schema.Provider
31+
// TestExternalProviders defines external providers (by name and source) required for acceptance tests.
32+
TestExternalProviders map[string]resource.ExternalProvider
33+
// TestAccFrameworkProvider is the FrameworkProvider instance used for advanced acceptance test scenarios.
2634
TestAccFrameworkProvider *provider.FrameworkProvider
2735
// testAccProviderConfigure ensures Provider is only configured once
2836
//
@@ -46,6 +54,9 @@ func init() {
4654
TestAccFrameworkProvider = provider.CreateFrameworkProvider(version.ProviderVersion).(*provider.FrameworkProvider)
4755
}
4856

57+
// TestAccPreCheck verifies that the required environment variables are set
58+
// for running acceptance tests. It checks for authentication credentials for
59+
// Equinix Fabric, Network Edge, and Metal services.
4960
func TestAccPreCheck(t *testing.T) {
5061
var err error
5162

@@ -54,24 +65,45 @@ func TestAccPreCheck(t *testing.T) {
5465
if err == nil {
5566
_, err = env.Get(config.ClientSecretEnvVar)
5667
}
68+
69+
// If neither token nor client ID/secret are configured, check for STS source token
70+
if err != nil {
71+
_, tokenExchangeScopeErr := env.Get(config.TokenExchangeScopeEnvVar)
72+
73+
// Check if either the custom env var name is set, or the default source token is set
74+
_, subjectTokenEnvVarErr := env.Get(config.TokenExchangeSubjectTokenEnvVarEnvVar)
75+
_, defaultSubjectTokenErr := env.Get(config.DefaultTokenExchangeSubjectTokenEnvVar)
76+
77+
if tokenExchangeScopeErr == nil && (subjectTokenEnvVarErr == nil || defaultSubjectTokenErr == nil) {
78+
err = nil
79+
}
80+
}
5781
}
5882

5983
if err == nil {
6084
_, err = env.Get(config.MetalAuthTokenEnvVar)
6185
}
6286

6387
if err != nil {
64-
t.Fatalf("To run acceptance tests, one of '%s' or pair '%s' - '%s' must be set for Equinix Fabric and Network Edge, and '%s' for Equinix Metal",
65-
config.ClientTokenEnvVar, config.ClientIDEnvVar, config.ClientSecretEnvVar, config.MetalAuthTokenEnvVar)
88+
t.Fatalf("To run acceptance tests, one of '%s', pair '%s' - '%s', or pair '%s' - ('%s' or custom env var from '%s') must be set for Equinix Fabric and Network Edge, and '%s' for Equinix Metal",
89+
config.ClientTokenEnvVar, config.ClientIDEnvVar, config.ClientSecretEnvVar,
90+
config.TokenExchangeScopeEnvVar, config.DefaultTokenExchangeSubjectTokenEnvVar,
91+
config.TokenExchangeSubjectTokenEnvVarEnvVar, config.MetalAuthTokenEnvVar)
6692
}
93+
6794
}
6895

96+
// TestAccPreCheckMetal specifically verifies that the Equinix Metal authentication token
97+
// environment variable is set for running Metal-specific acceptance tests.
6998
func TestAccPreCheckMetal(t *testing.T) {
7099
if os.Getenv(config.MetalAuthTokenEnvVar) == "" {
71100
t.Fatalf(missingMetalToken, config.MetalAuthTokenEnvVar)
72101
}
73102
}
74103

104+
// TestAccPreCheckProviderConfigured ensures the provider is properly configured
105+
// before running tests. It uses sync.Once to guarantee the provider is
106+
// configured exactly once across all test executions.
75107
func TestAccPreCheckProviderConfigured(t *testing.T) {
76108
// Since we are outside the scope of the Terraform configuration we must
77109
// call Configure() to properly initialize the provider configuration.

0 commit comments

Comments
 (0)