Skip to content

Commit c656c5b

Browse files
committed
fix: populate admin_features_package and product_types in ec_security_project Read()
The Read() function in ec_security_project was not populating the admin_features_package and product_types fields from the API response, causing them to remain in an 'unknown' state after resource creation. This resulted in Terraform errors: 'Provider returned invalid result object after apply'. This fix adds the missing field population logic in the Read() function: - admin_features_package uses NewStringPointerValue() pattern (matching observability_project pattern for optional pointer fields) - product_types is converted from API response format to Terraform list Updated tests to verify the fields are correctly populated. Fixes #938 Assisted by Cursor AI.
1 parent 5e67580 commit c656c5b

File tree

2 files changed

+150
-6
lines changed

2 files changed

+150
-6
lines changed

ec/ecresource/projectresource/security.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,43 @@ func (sec securityApi) Read(ctx context.Context, id string, model resource_secur
288288
model.RegionId = basetypes.NewStringValue(resp.JSON200.RegionId)
289289
model.Type = basetypes.NewStringValue(string(resp.JSON200.Type))
290290

291+
// Populate admin_features_package from API response (matching pattern used for suspended_reason)
292+
var adminFeaturesPkg *string
293+
if resp.JSON200.AdminFeaturesPackage != nil {
294+
pkgStr := string(*resp.JSON200.AdminFeaturesPackage)
295+
adminFeaturesPkg = &pkgStr
296+
}
297+
model.AdminFeaturesPackage = basetypes.NewStringPointerValue(adminFeaturesPkg)
298+
299+
// Populate product_types from API response
300+
if resp.JSON200.ProductTypes != nil {
301+
productTypeValues := []attr.Value{}
302+
for _, pt := range *resp.JSON200.ProductTypes {
303+
productTypeValue, diags := resource_security_project.NewProductTypesValue(
304+
resource_security_project.ProductTypesValue{}.AttributeTypes(ctx),
305+
map[string]attr.Value{
306+
"product_line": basetypes.NewStringValue(string(pt.ProductLine)),
307+
"product_tier": basetypes.NewStringValue(string(pt.ProductTier)),
308+
},
309+
)
310+
if diags.HasError() {
311+
return false, model, diags
312+
}
313+
productTypeValues = append(productTypeValues, productTypeValue)
314+
}
315+
316+
productTypesList, diags := types.ListValue(
317+
resource_security_project.ProductTypesValue{}.Type(ctx),
318+
productTypeValues,
319+
)
320+
if diags.HasError() {
321+
return false, model, diags
322+
}
323+
model.ProductTypes = productTypesList
324+
} else {
325+
model.ProductTypes = types.ListNull(resource_security_project.ProductTypesValue{}.Type(ctx))
326+
}
327+
291328
return true, model, nil
292329
}
293330

ec/ecresource/projectresource/security_test.go

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -904,9 +904,11 @@ func TestSecurityApi_Read(t *testing.T) {
904904
"suspended_reason": basetypes.NewStringNull(),
905905
},
906906
),
907-
Name: types.StringValue(readModel.Name),
908-
RegionId: types.StringValue(readModel.RegionId),
909-
Type: types.StringValue(string(readModel.Type)),
907+
Name: types.StringValue(readModel.Name),
908+
RegionId: types.StringValue(readModel.RegionId),
909+
Type: types.StringValue(string(readModel.Type)),
910+
AdminFeaturesPackage: basetypes.NewStringNull(),
911+
ProductTypes: types.ListNull(resource_security_project.ProductTypesValue{}.Type(ctx)),
910912
}
911913

912914
mockApiClient := mocks.NewMockClientWithResponsesInterface(ctrl)
@@ -977,9 +979,114 @@ func TestSecurityApi_Read(t *testing.T) {
977979
"suspended_reason": basetypes.NewStringValue(*readModel.Metadata.SuspendedReason),
978980
},
979981
),
980-
Name: types.StringValue(readModel.Name),
981-
RegionId: types.StringValue(readModel.RegionId),
982-
Type: types.StringValue(string(readModel.Type)),
982+
Name: types.StringValue(readModel.Name),
983+
RegionId: types.StringValue(readModel.RegionId),
984+
Type: types.StringValue(string(readModel.Type)),
985+
AdminFeaturesPackage: basetypes.NewStringNull(),
986+
ProductTypes: types.ListNull(resource_security_project.ProductTypesValue{}.Type(ctx)),
987+
}
988+
989+
mockApiClient := mocks.NewMockClientWithResponsesInterface(ctrl)
990+
mockApiClient.EXPECT().
991+
GetSecurityProjectWithResponse(ctx, id).
992+
Return(&serverless.GetSecurityProjectResponse{
993+
JSON200: readModel,
994+
}, nil)
995+
996+
return testData{
997+
client: mockApiClient,
998+
id: id,
999+
initialModel: initialModel,
1000+
expectedModel: expectedModel,
1001+
expectedFound: true,
1002+
}
1003+
},
1004+
},
1005+
{
1006+
name: "should populate admin_features_package and product_types when provided in response",
1007+
testData: func(ctx context.Context) testData {
1008+
id := "project id"
1009+
initialModel := resource_security_project.SecurityProjectModel{
1010+
Id: types.StringValue(id),
1011+
}
1012+
1013+
adminFeaturesPackage := serverless.SecurityAdminFeaturesPackage("enterprise")
1014+
productTypes := []serverless.SecurityProductType{
1015+
{
1016+
ProductLine: "security",
1017+
ProductTier: "complete",
1018+
},
1019+
{
1020+
ProductLine: "cloud",
1021+
ProductTier: "complete",
1022+
},
1023+
}
1024+
1025+
readModel := &serverless.SecurityProject{
1026+
Id: id,
1027+
Alias: "expected-alias-" + id[0:6],
1028+
CloudId: "cloud-id",
1029+
Endpoints: serverless.SecurityProjectEndpoints{
1030+
Elasticsearch: "es-endpoint",
1031+
Kibana: "kib-endpoint",
1032+
Ingest: "ingest-endpoint",
1033+
},
1034+
Metadata: serverless.ProjectMetadata{
1035+
CreatedAt: time.Now(),
1036+
CreatedBy: "me",
1037+
OrganizationId: "1",
1038+
},
1039+
Name: "project-name",
1040+
RegionId: "nether",
1041+
Type: "security",
1042+
AdminFeaturesPackage: &adminFeaturesPackage,
1043+
ProductTypes: &productTypes,
1044+
}
1045+
1046+
expectedProductTypes := []attr.Value{
1047+
resource_security_project.NewProductTypesValueMust(
1048+
resource_security_project.ProductTypesValue{}.AttributeTypes(ctx),
1049+
map[string]attr.Value{
1050+
"product_line": basetypes.NewStringValue("security"),
1051+
"product_tier": basetypes.NewStringValue("complete"),
1052+
},
1053+
),
1054+
resource_security_project.NewProductTypesValueMust(
1055+
resource_security_project.ProductTypesValue{}.AttributeTypes(ctx),
1056+
map[string]attr.Value{
1057+
"product_line": basetypes.NewStringValue("cloud"),
1058+
"product_tier": basetypes.NewStringValue("complete"),
1059+
},
1060+
),
1061+
}
1062+
1063+
expectedModel := resource_security_project.SecurityProjectModel{
1064+
Id: types.StringValue(id),
1065+
Alias: types.StringValue("expected-alias"),
1066+
CloudId: types.StringValue(readModel.CloudId),
1067+
Endpoints: resource_security_project.NewEndpointsValueMust(
1068+
initialModel.Endpoints.AttributeTypes(ctx),
1069+
map[string]attr.Value{
1070+
"elasticsearch": basetypes.NewStringValue(readModel.Endpoints.Elasticsearch),
1071+
"kibana": basetypes.NewStringValue(readModel.Endpoints.Kibana),
1072+
"ingest": basetypes.NewStringValue(readModel.Endpoints.Ingest),
1073+
},
1074+
),
1075+
Metadata: resource_security_project.NewMetadataValueMust(
1076+
initialModel.Metadata.AttributeTypes(ctx),
1077+
map[string]attr.Value{
1078+
"created_at": basetypes.NewStringValue(readModel.Metadata.CreatedAt.String()),
1079+
"created_by": basetypes.NewStringValue(readModel.Metadata.CreatedBy),
1080+
"organization_id": basetypes.NewStringValue(readModel.Metadata.OrganizationId),
1081+
"suspended_at": basetypes.NewStringNull(),
1082+
"suspended_reason": basetypes.NewStringNull(),
1083+
},
1084+
),
1085+
Name: types.StringValue(readModel.Name),
1086+
RegionId: types.StringValue(readModel.RegionId),
1087+
Type: types.StringValue(string(readModel.Type)),
1088+
AdminFeaturesPackage: basetypes.NewStringValue("enterprise"),
1089+
ProductTypes: types.ListValueMust(resource_security_project.ProductTypesValue{}.Type(ctx), expectedProductTypes),
9831090
}
9841091

9851092
mockApiClient := mocks.NewMockClientWithResponsesInterface(ctrl)

0 commit comments

Comments
 (0)