Hi Team,
I’m working on developing a custom Terraform provider for our internal infrastructure, which utilizes a REST API for management. The API’s CREATE
endpoint expects the following input:
resource "test_environment" "edu1" {
name = "test"
region = "us-central"
password = "dummy"
}
The REST API response is:
{
"name": "test",
"region": "us-central",
"state": "RUNNING",
"ip": "127.0.0.1",
"dnsName": "test.env.xx..com",
"owner": "ss@xx.com",
"type": "xx",
"services": [
{
"name": "test",
"credentials": [
{
"name": "username",
"value": "xx"
},
{
"name": "password",
"value": "xx"
}
]
}
]
}
Here is the schema I’ve defined:
func (r *environmentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
Description: "The name of the environment.",
},
"last_updated": schema.StringAttribute{
Computed: true,
Description: "The last time the environment was updated.",
},
"operation": schema.StringAttribute{
Computed: true,
Description: "The last operation performed on the environment.",
},
"region": schema.StringAttribute{
Required: true,
Description: "The region of the environment.",
},
"password": schema.StringAttribute{
Required: true,
Sensitive: true,
Description: "The password for the environment.",
},
"state": schema.StringAttribute{
Computed: true,
Description: "The current state of the environment.",
},
"ip": schema.StringAttribute{
Computed: true,
Description: "The IP address of the environment.",
},
"dnsname": schema.StringAttribute{
Computed: true,
Description: "The DNS name of the environment.",
},
"owner": schema.StringAttribute{
Computed: true,
Description: "The owner of the environment.",
},
"type": schema.StringAttribute{
Computed: true,
Description: "The type of the environment.",
},
"services": schema.ListNestedAttribute{
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Computed: true,
Description: "The name of the service.",
},
"url": schema.StringAttribute{
Computed: true,
Description: "The URL of the service.",
},
"credentials": schema.ListNestedAttribute{
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Computed: true,
Description: "The name of the credential.",
},
"value": schema.StringAttribute{
Computed: true,
Description: "The value of the credential.",
},
},
},
},
},
},
},
},
}
}
And the models are defined as:
type environmentCredentialModel struct {
Name types.String `tfsdk:"name"`
Value types.String `tfsdk:"value"`
}
type environmentServiceModel struct {
Name types.String `tfsdk:"name"`
URL types.String `tfsdk:"url"`
Credentials []environmentCredentialModel `tfsdk:"credentials"`
}
type environmentResourceModel struct {
Name types.String `tfsdk:"name"`
Region types.String `tfsdk:"region"`
State types.String `tfsdk:"state"`
IP types.String `tfsdk:"ip"`
DNSName types.String `tfsdk:"dnsname"`
Owner types.String `tfsdk:"owner"`
Type types.String `tfsdk:"type"`
LastUpdated types.String `tfsdk:"last_updated"`
Operation types.String `tfsdk:"operation"`
Password types.String `tfsdk:"password"`
Services []environmentServiceModel `tfsdk:"services"`
}
Here my Create method
func (r *environmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan environmentResourceModel
**diags := req.Plan.Get(ctx, &plan)** // here it is failing
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var envRequest client.EnvironmentCreateRequest
envRequest.Name = plan.Name.ValueString()
envRequest.Region = plan.Region.ValueString()
envRequest.Password = plan.Password.ValueString()
env, err := r.client.CreateEnvironment(envRequest)
if err != nil {
resp.Diagnostics.AddError("Failed to create environment", err.Error())
return
}
environment := environmentResourceModel{
Name: types.StringValue(env.Name),
Region: types.StringValue(env.Region),
State: types.StringValue(env.State),
IP: types.StringValue(env.IP),
DNSName: types.StringValue(env.DNSName),
Owner: types.StringValue(env.Owner),
Type: types.StringValue(env.Type),
Password: types.StringValue(envRequest.Password),
}
for _, service := range env.Services {
s := environmentServiceModel{
Name: types.StringValue(service.Name),
URL: types.StringValue(service.URL),
}
for _, cred := range service.Credentials {
s.Credentials = append(s.Credentials, environmentCredentialModel{
Name: types.StringValue(cred.Name),
Value: types.StringValue(cred.Value),
})
}
environment.Services = append(environment.Services, s)
}
plan = environment
// Set state to fully populated data
diags = resp.State.Set(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
The terraform configuration file is main.tf:
resource "test_environment" "edu1" {
name = "teste"
region = "us-central"
password = "environment"
}
However, I’m encountering the following error:
test_environment.edu1: Creating...
╷
│ Error: Value Conversion Error
│
│ with test_environment.edu1,
│ on main.tf line 13, in resource "test_environment" "edu1":
│ 13: resource "test_environment" "edu1" {
│
│ An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:
│
│ Received unknown value, however the target type cannot handle unknown values. Use the corresponding `types` package type or a custom type that handles unknown values.
│
│ Path: services
│ Target Type: []provider.environmentServiceModel
│ Suggested Type: basetypes.ListValue
Any insights or suggestions on how to resolve this issue would be greatly appreciated!