Repository: opentofu/opentofu
File: internal/tofu/eval_provider.go (Go)

Code before:
	}
	if keyVal.IsNull() {
		return nil, diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider instance key",
			Detail:   "A provider instance key must not be null.",
			Subject:  keyExpr.Range().Ptr(),
		})
	}
	if !keyVal.IsKnown() {
		return nil, diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider instance key",
			Detail:   fmt.Sprintf("The provider instance key for %s depends on values that cannot be determined until apply, and so OpenTofu cannot select a provider instance to create a plan for this resource instance.", source),
			Subject:  keyExpr.Range().Ptr(),
			Extra:    evalchecks.DiagnosticCausedByUnknown(true),
		})
	}

	// bool and number type are converted to string
	if keyVal.Type() == cty.Bool {
		keyVal = cty.StringVal(fmt.Sprintf("%t", keyVal.True())) // using type to convert the bool
	} else if keyVal.Type() == cty.Number {
		keyVal = cty.StringVal(fmt.Sprintf("%v", keyVal.AsBigFloat())) // using value to convert the number
	}

	parsedKey, parsedErr := addrs.ParseInstanceKey(keyVal)
	if parsedErr != nil {
		return nil, diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider instance key",
			Detail:   fmt.Sprintf("The given instance key is unsuitable: %s.", tfdiags.FormatError(parsedErr)),
			Subject:  keyExpr.Range().Ptr(),
		})
	}
	return parsedKey, diags
}

// getProvider returns the providers.Interface and schema for a given provider.
func getProvider(ctx EvalContext, addr addrs.AbsProviderConfig, providerKey addrs.InstanceKey) (providers.Interface, providers.ProviderSchema, error) {
	if addr.Provider.Type == "" {
		// Should never happen
		panic("GetProvider used with uninitialized provider configuration address")
	}
	provider := ctx.Provider(addr, providerKey)
	if provider == nil {
		return nil, providers.ProviderSchema{}, fmt.Errorf("provider %s not initialized", addr.InstanceString(providerKey))
	}
	// Not all callers require a schema, so we will leave checking for a nil
	// schema to the callers.
	schema, err := ctx.ProviderSchema(addr)

Review comment (bug):
If you import the `github.com/zclconf/go-cty/cty/convert` package in this file then you can use it to perform this conversion in the same way that other parts of OpenTofu do it:

```go
keyVal, convertErr := convert.Convert(keyVal, cty.String)
if convertErr != nil {
	return nil, diags.Append(&hcl.Diagnostic{
		Severity: hcl.DiagError,
		Summary:  "Invalid provider instance key",
		Detail:   fmt.Sprintf("The given instance key is unsuitable: %s.", tfdiags.FormatError(convertErr)),
		Subject:  keyExpr.Range().Ptr(),
	})
}
```

Interestingly, with that conversion in place it would become impossible for the subsequent `addrs.ParseInstanceKey` to fail because `keyVal` will be guaranteed to be a string:

https://github.com/opentofu/opentofu/blob/0d57aa4f35998bf2847ac3acb343f9e16b5c2995/internal/addrs/instance_key.go#L41-L52

However, I think we should still retain the error handling for `parsedErr` as written below because the implementation of `addrs.ParseInstanceKey` could potentially change in future.


Code after:
		})
	}
	if keyVal.IsNull() {
		return nil, diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider instance key",
			Detail:   "A provider instance key must not be null.",
			Subject:  keyExpr.Range().Ptr(),
		})
	}
	if !keyVal.IsKnown() {
		return nil, diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider instance key",
			Detail:   fmt.Sprintf("The provider instance key for %s depends on values that cannot be determined until apply, and so OpenTofu cannot select a provider instance to create a plan for this resource instance.", source),
			Subject:  keyExpr.Range().Ptr(),
			Extra:    evalchecks.DiagnosticCausedByUnknown(true),
		})
	}

	// bool and number type are converted to string
	keyVal, convertErr := convert.Convert(keyVal, cty.String)
	if convertErr != nil {
		return nil, diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider instance key",
			Detail:   fmt.Sprintf("The given instance key is unsuitable: %s.", tfdiags.FormatError(convertErr)),
			Subject:  keyExpr.Range().Ptr(),
		})
	}

	// Because of the string conversion before the call of the ParseInstanceKey function,
	// no errors will be raised. Because keyVal is guaranteed to be a string.
	// We can keep the error handling in case the implementation of ParseInstanceKey change in the future
	parsedKey, parsedErr := addrs.ParseInstanceKey(keyVal)
	if parsedErr != nil {
		return nil, diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider instance key",
			Detail:   fmt.Sprintf("The given instance key is unsuitable: %s.", tfdiags.FormatError(parsedErr)),
			Subject:  keyExpr.Range().Ptr(),
		})
	}
	return parsedKey, diags
}

// getProvider returns the providers.Interface and schema for a given provider.
func getProvider(ctx EvalContext, addr addrs.AbsProviderConfig, providerKey addrs.InstanceKey) (providers.Interface, providers.ProviderSchema, error) {
	if addr.Provider.Type == "" {
		// Should never happen
		panic("GetProvider used with uninitialized provider configuration address")