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

Code before:
			Summary:  "Invalid provider instance key",
			Detail:   "A provider instance key must not be derived from a sensitive value.",
			Subject:  keyExpr.Range().Ptr(),
			Extra:    evalchecks.DiagnosticCausedBySensitive(true),
		})
	}
	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.Type() == cty.Number {
		keyConvert, convertErr := convert.Convert(keyVal, cty.String) // Conversion from bool or number to 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(),
			})
		}
		keyVal = keyConvert
	}

	// 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

Review comment (suggestion):
Hello again!

Sorry I wasn't clear about this in my previous comment, but if we use `convert.Convert` here we should no longer need any checking of the type before the call. If `keyVal` is already a string then `convert.Convert` will just return it unchanged, and `convert.Convert` will return an error itself if any other type is passed, so we should be able to call `convert.Convert` completely unconditionally here, regardless of the type of `keyVal`.

Here's some other existing code following a similar pattern, for example:

https://github.com/opentofu/opentofu/blob/b6e29b0c4b1a7273352a49796543760d3f58771a/internal/configs/version_constraint.go#L50-L60

(I've limited this snippet only to the `convert.Convert` call and its error handling, but if you click through to the full file this snippet is from you can see that it's a very similar pattern where it takes an arbitrary value given by the user, converts it to string, and then tries to parse it in a special way. In that example it's parsed as a version constraint string rather than as an instance key, but the general pattern is the same.)


Code after:
			Summary:  "Invalid provider instance key",
			Detail:   "A provider instance key must not be derived from a sensitive value.",
			Subject:  keyExpr.Range().Ptr(),
			Extra:    evalchecks.DiagnosticCausedBySensitive(true),
		})
	}
	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.