@@ -133,7 +133,9 @@ func (p *pcdProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *
133133 "cloud" : schema.StringAttribute {
134134 Optional : true ,
135135 MarkdownDescription : "Name of a `clouds.yaml` entry to source configuration from. Falls back to `OS_CLOUD`. " +
136- "**Not yet implemented in this pre-release** — use explicit `auth_url`/credentials for now." ,
136+ "The file is searched at `$OS_CLIENT_CONFIG_FILE`, `./clouds.yaml`, " +
137+ "`~/.config/openstack/clouds.yaml`, then `/etc/openstack/clouds.yaml`. Explicit provider " +
138+ "arguments and `OS_*` environment variables override values from the file." ,
137139 },
138140 "endpoint_overrides" : schema.MapAttribute {
139141 Optional : true ,
@@ -159,36 +161,41 @@ func (p *pcdProvider) Configure(ctx context.Context, req provider.ConfigureReque
159161 return
160162 }
161163
162- // clouds.yaml support is declared in the schema for config parity but not yet
163- // wired. Fail loudly rather than silently ignore a user who sets it.
164- if strval (m .Cloud , "OS_CLOUD" ) != "" {
165- resp .Diagnostics .AddAttributeError (
166- path .Root ("cloud" ),
167- "clouds.yaml (cloud) not yet supported" ,
168- "This pre-release does not yet source configuration from clouds.yaml. " +
169- "Configure auth_url and credentials explicitly (or via OS_* env vars)." ,
170- )
171- return
164+ // When `cloud` (or OS_CLOUD) is set, source auth defaults from that clouds.yaml
165+ // entry. Precedence is explicit config > OS_* env > clouds.yaml, so the loaded
166+ // values are the lowest tier of fallbacks.
167+ cv := clients.CloudConfig {}
168+ insecureDefault := false
169+ if cloudName := strval (m .Cloud , "OS_CLOUD" ); cloudName != "" {
170+ cloud , err := clients .LoadCloud (cloudName )
171+ if err != nil {
172+ resp .Diagnostics .AddAttributeError (path .Root ("cloud" ), "Failed to load clouds.yaml" , err .Error ())
173+ return
174+ }
175+ cv = * cloud
176+ if cv .HasInsecure {
177+ insecureDefault = cv .Insecure
178+ }
172179 }
173180
174181 cfg := & clients.Config {
175- AuthURL : strval ( m .AuthURL , "OS_AUTH_URL" ),
176- Region : strval ( m .Region , "OS_REGION_NAME" ),
177- Username : strval (m .UserName , "OS_USERNAME" ),
178- UserID : strval ( m .UserID , "OS_USER_ID" ),
179- Password : strval ( m .Password , "OS_PASSWORD" ),
180- TenantName : strval ( m .TenantName , "OS_PROJECT_NAME" , "OS_TENANT_NAME" ),
181- TenantID : strval ( m .TenantID , "OS_PROJECT_ID" , "OS_TENANT_ID" ),
182- UserDomainID : strval ( m .UserDomainID , "OS_USER_DOMAIN_ID" ),
183- UserDomainName : strval ( m .UserDomainName , "OS_USER_DOMAIN_NAME" ),
184- ProjectDomainID : strval ( m .ProjectDomainID , "OS_PROJECT_DOMAIN_ID" ),
185- ProjectDomainName : strval ( m .ProjectDomainName , "OS_PROJECT_DOMAIN_NAME" ),
186- Token : strval ( m .Token , "OS_TOKEN" , "OS_AUTH_TOKEN" ),
187- AppCredID : strval ( m .AppCredID , "OS_APPLICATION_CREDENTIAL_ID" ),
188- AppCredName : strval ( m .AppCredName , "OS_APPLICATION_CREDENTIAL_NAME" ),
189- AppCredSecret : strval ( m .AppCredSecret , "OS_APPLICATION_CREDENTIAL_SECRET" ),
190- Insecure : boolval (m .Insecure , false , "OS_INSECURE" ),
191- CACertFile : strval ( m .CACertFile , "OS_CACERT" ),
182+ AuthURL : pick ( m . AuthURL , cv .AuthURL , "OS_AUTH_URL" ),
183+ Region : pick ( m . Region , cv .Region , "OS_REGION_NAME" ),
184+ Username : pick (m .UserName , cv . Username , "OS_USERNAME" ),
185+ UserID : pick ( m . UserID , cv .UserID , "OS_USER_ID" ),
186+ Password : pick ( m . Password , cv .Password , "OS_PASSWORD" ),
187+ TenantName : pick ( m . TenantName , cv .TenantName , "OS_PROJECT_NAME" , "OS_TENANT_NAME" ),
188+ TenantID : pick ( m . TenantID , cv .TenantID , "OS_PROJECT_ID" , "OS_TENANT_ID" ),
189+ UserDomainID : pick ( m . UserDomainID , cv .UserDomainID , "OS_USER_DOMAIN_ID" ),
190+ UserDomainName : pick ( m . UserDomainName , cv .UserDomainName , "OS_USER_DOMAIN_NAME" ),
191+ ProjectDomainID : pick ( m . ProjectDomainID , cv .ProjectDomainID , "OS_PROJECT_DOMAIN_ID" ),
192+ ProjectDomainName : pick ( m . ProjectDomainName , cv .ProjectDomainName , "OS_PROJECT_DOMAIN_NAME" ),
193+ Token : pick ( m . Token , cv .Token , "OS_TOKEN" , "OS_AUTH_TOKEN" ),
194+ AppCredID : pick ( m . AppCredID , cv .AppCredID , "OS_APPLICATION_CREDENTIAL_ID" ),
195+ AppCredName : pick ( m . AppCredName , cv .AppCredName , "OS_APPLICATION_CREDENTIAL_NAME" ),
196+ AppCredSecret : pick ( m . AppCredSecret , cv .AppCredSecret , "OS_APPLICATION_CREDENTIAL_SECRET" ),
197+ Insecure : boolval (m .Insecure , insecureDefault , "OS_INSECURE" ),
198+ CACertFile : pick ( m . CACertFile , cv .CACertFile , "OS_CACERT" ),
192199 ClientCertFile : strval (m .Cert , "OS_CERT" ),
193200 ClientKeyFile : strval (m .Key , "OS_KEY" ),
194201 AllowReauth : boolval (m .AllowReauth , true ),
@@ -223,9 +230,21 @@ func (p *pcdProvider) Configure(ctx context.Context, req provider.ConfigureReque
223230 resp .ResourceData = cfg
224231}
225232
226- // strval returns the configured value if set, otherwise the first non-empty env var.
233+ // pick resolves a value with precedence: explicit config value, then the first
234+ // non-empty environment variable, then the clouds.yaml fallback.
235+ func pick (v types.String , fallback string , envVars ... string ) string {
236+ if s := strval (v , envVars ... ); s != "" {
237+ return s
238+ }
239+ return fallback
240+ }
241+
242+ // strval returns the configured value if set to a non-empty string, otherwise
243+ // the first non-empty env var. An explicitly-empty config value (e.g. a variable
244+ // defaulting to "") is treated as unset so the env fallback still applies —
245+ // matching terraform-provider-openstack's env-default behavior.
227246func strval (v types.String , envVars ... string ) string {
228- if ! v .IsNull () && ! v .IsUnknown () {
247+ if ! v .IsNull () && ! v .IsUnknown () && v . ValueString () != "" {
229248 return v .ValueString ()
230249 }
231250 for _ , e := range envVars {
0 commit comments