Skip to content

Commit 4cf875d

Browse files
CID-517 - Add child_org_id to Batch Update Enterprise Group Role Bindings (#664)
Support role bindings for enterprise groups
1 parent 0e9cf47 commit 4cf875d

File tree

8 files changed

+244
-38
lines changed

8 files changed

+244
-38
lines changed

castai/resource_enterprise_role_binding.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ func buildBatchCreateRoleBindingRequest(data *schema.ResourceData, enterpriseID
417417
return nil, fmt.Errorf("reading role binding data: %w", err)
418418
}
419419

420-
definition := buildRoleBindingDefinition(roleBinding)
420+
definition := buildRoleBindingDefinition(roleBinding, enterpriseID)
421421

422422
request := organization_management.BatchCreateEnterpriseRoleBindingsRequest{
423423
EnterpriseId: enterpriseID,
@@ -442,7 +442,7 @@ func buildBatchUpdateRoleBindingRequest(data *schema.ResourceData, enterpriseID,
442442
return nil, fmt.Errorf("reading role binding data: %w", err)
443443
}
444444

445-
definition := buildRoleBindingDefinition(roleBinding)
445+
definition := buildRoleBindingDefinition(roleBinding, enterpriseID)
446446

447447
request := organization_management.BatchUpdateEnterpriseRoleBindingsRequest{
448448
EnterpriseId: enterpriseID,
@@ -460,14 +460,20 @@ func buildBatchUpdateRoleBindingRequest(data *schema.ResourceData, enterpriseID,
460460
return &request, nil
461461
}
462462

463-
func buildRoleBindingDefinition(roleBinding EnterpriseRoleBinding) organization_management.RoleBindingDefinition {
463+
func buildRoleBindingDefinition(roleBinding EnterpriseRoleBinding, enterpriseID string) organization_management.RoleBindingDefinition {
464464
subjects := convertEnterpriseRoleBindingSubjectsToSDK(roleBinding.Subjects)
465465
scopes := convertEnterpriseRoleBindingScopesToSDK(roleBinding.Scopes)
466466

467467
definition := organization_management.RoleBindingDefinition{
468468
RoleId: lo.ToPtr(roleBinding.RoleID),
469469
}
470470

471+
// When the role binding targets a child organization, set ChildOrganizationId so the
472+
// API validates role_id against the child org's roles instead of the enterprise's roles.
473+
if roleBinding.OrganizationID != enterpriseID {
474+
definition.ChildOrganizationId = lo.ToPtr(roleBinding.OrganizationID)
475+
}
476+
471477
if len(subjects) > 0 {
472478
definition.Subjects = &subjects
473479
}

castai/resource_enterprise_role_binding_test.go

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ func TestResourceEnterpriseRoleBindingCreateContext(t *testing.T) {
129129
Name: "engineering-viewer",
130130
Description: lo.ToPtr("Engineering viewer role binding"),
131131
Definition: organization_management.RoleBindingDefinition{
132-
RoleId: lo.ToPtr(roleID),
132+
RoleId: lo.ToPtr(roleID),
133+
ChildOrganizationId: lo.ToPtr(organizationID1),
133134
Subjects: &[]organization_management.Subject{
134135
{
135136
User: &organization_management.UserSubject{
@@ -511,6 +512,121 @@ func TestResourceEnterpriseRoleBindingCreateContext(t *testing.T) {
511512
r.True(result.HasError())
512513
r.Contains(result[0].Summary, "at least one scope (organization or cluster) must be defined")
513514
})
515+
516+
t.Run("when organization_id differs from enterprise_id then ChildOrganizationId is set in definition", func(t *testing.T) {
517+
t.Parallel()
518+
r := require.New(t)
519+
mockClient := mockOrganizationManagement.NewMockClientWithResponsesInterface(gomock.NewController(t))
520+
521+
ctx := context.Background()
522+
provider := &ProviderConfig{
523+
organizationManagementClient: mockClient,
524+
}
525+
526+
enterpriseID := uuid.NewString()
527+
childOrgID := uuid.NewString() // different from enterpriseID
528+
roleBindingID := uuid.NewString()
529+
roleID := uuid.NewString()
530+
userID := uuid.NewString()
531+
532+
expectedCreateRequest := organization_management.BatchCreateEnterpriseRoleBindingsRequest{
533+
EnterpriseId: enterpriseID,
534+
Requests: []organization_management.BatchCreateEnterpriseRoleBindingsRequestCreateRoleBindingRequest{
535+
{
536+
OrganizationId: childOrgID,
537+
RoleBinding: organization_management.BatchCreateEnterpriseRoleBindingsRequestRoleBinding{
538+
Name: "child-org-binding",
539+
Description: lo.ToPtr(""),
540+
Definition: organization_management.RoleBindingDefinition{
541+
RoleId: lo.ToPtr(roleID),
542+
ChildOrganizationId: lo.ToPtr(childOrgID),
543+
Subjects: &[]organization_management.Subject{
544+
{
545+
User: &organization_management.UserSubject{Id: userID},
546+
},
547+
},
548+
Scopes: &[]organization_management.Scope{
549+
{
550+
Organization: &organization_management.OrganizationScope{Id: childOrgID},
551+
},
552+
},
553+
},
554+
},
555+
},
556+
},
557+
}
558+
559+
mockClient.EXPECT().
560+
EnterpriseAPIBatchCreateEnterpriseRoleBindingsWithResponse(gomock.Any(), enterpriseID, expectedCreateRequest).
561+
Return(&organization_management.EnterpriseAPIBatchCreateEnterpriseRoleBindingsResponse{
562+
Body: nil,
563+
HTTPResponse: &http.Response{StatusCode: http.StatusOK},
564+
JSON200: &organization_management.BatchCreateEnterpriseRoleBindingsResponse{
565+
RoleBindings: &[]organization_management.RoleBinding{
566+
{
567+
Id: lo.ToPtr(roleBindingID),
568+
Name: lo.ToPtr("child-org-binding"),
569+
OrganizationId: lo.ToPtr(childOrgID),
570+
Definition: &organization_management.RoleBindingDefinition{
571+
RoleId: lo.ToPtr(roleID),
572+
ChildOrganizationId: lo.ToPtr(childOrgID),
573+
Subjects: &[]organization_management.Subject{
574+
{User: &organization_management.UserSubject{Id: userID}},
575+
},
576+
Scopes: &[]organization_management.Scope{
577+
{Organization: &organization_management.OrganizationScope{Id: childOrgID}},
578+
},
579+
},
580+
},
581+
},
582+
},
583+
}, nil)
584+
585+
stateValue := cty.ObjectVal(map[string]cty.Value{
586+
FieldEnterpriseRoleBindingEnterpriseID: cty.StringVal(enterpriseID),
587+
FieldEnterpriseRoleBindingOrganizationID: cty.StringVal(childOrgID),
588+
FieldEnterpriseRoleBindingName: cty.StringVal("child-org-binding"),
589+
FieldEnterpriseRoleBindingDescription: cty.StringVal(""),
590+
FieldEnterpriseRoleBindingRoleID: cty.StringVal(roleID),
591+
FieldEnterpriseRoleBindingSubjects: cty.ListVal([]cty.Value{
592+
cty.ObjectVal(map[string]cty.Value{
593+
FieldEnterpriseRoleBindingSubjectUser: cty.ListVal([]cty.Value{
594+
cty.ObjectVal(map[string]cty.Value{
595+
FieldEnterpriseRoleBindingSubjectID: cty.StringVal(userID),
596+
}),
597+
}),
598+
FieldEnterpriseRoleBindingSubjectServiceAccount: cty.ListValEmpty(cty.Object(map[string]cty.Type{
599+
FieldEnterpriseRoleBindingSubjectID: cty.String,
600+
})),
601+
FieldEnterpriseRoleBindingSubjectGroup: cty.ListValEmpty(cty.Object(map[string]cty.Type{
602+
FieldEnterpriseRoleBindingSubjectID: cty.String,
603+
})),
604+
}),
605+
}),
606+
FieldEnterpriseRoleBindingScopes: cty.ListVal([]cty.Value{
607+
cty.ObjectVal(map[string]cty.Value{
608+
FieldEnterpriseRoleBindingScopeOrganization: cty.ListVal([]cty.Value{
609+
cty.ObjectVal(map[string]cty.Value{
610+
FieldEnterpriseRoleBindingScopeID: cty.StringVal(childOrgID),
611+
}),
612+
}),
613+
FieldEnterpriseRoleBindingScopeCluster: cty.ListValEmpty(cty.Object(map[string]cty.Type{
614+
FieldEnterpriseRoleBindingScopeID: cty.String,
615+
})),
616+
}),
617+
}),
618+
})
619+
state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
620+
621+
resource := resourceEnterpriseRoleBinding()
622+
data := resource.Data(state)
623+
624+
result := resource.CreateContext(ctx, data, provider)
625+
626+
r.Nil(result)
627+
r.False(result.HasError())
628+
r.Equal(roleBindingID, data.Id())
629+
})
514630
}
515631

516632
func TestResourceEnterpriseRoleBindingReadContext(t *testing.T) {
@@ -982,7 +1098,8 @@ func TestResourceEnterpriseRoleBindingUpdateContext(t *testing.T) {
9821098
OrganizationId: organizationID,
9831099
Description: lo.ToPtr("Updated description"),
9841100
Definition: organization_management.RoleBindingDefinition{
985-
RoleId: lo.ToPtr(newRoleID),
1101+
RoleId: lo.ToPtr(newRoleID),
1102+
ChildOrganizationId: lo.ToPtr(organizationID),
9861103
Subjects: &[]organization_management.Subject{
9871104
{
9881105
User: &organization_management.UserSubject{
@@ -1238,7 +1355,8 @@ func TestResourceEnterpriseRoleBindingUpdateContext(t *testing.T) {
12381355
OrganizationId: organizationID,
12391356
Description: lo.ToPtr("Test description"),
12401357
Definition: organization_management.RoleBindingDefinition{
1241-
RoleId: lo.ToPtr(roleID),
1358+
RoleId: lo.ToPtr(roleID),
1359+
ChildOrganizationId: lo.ToPtr(organizationID),
12421360
Subjects: &[]organization_management.Subject{
12431361
{
12441362
User: &organization_management.UserSubject{
@@ -1419,7 +1537,8 @@ func TestResourceEnterpriseRoleBindingUpdateContext(t *testing.T) {
14191537
OrganizationId: organizationID,
14201538
Description: lo.ToPtr("Test description"),
14211539
Definition: organization_management.RoleBindingDefinition{
1422-
RoleId: lo.ToPtr(roleID),
1540+
RoleId: lo.ToPtr(roleID),
1541+
ChildOrganizationId: lo.ToPtr(organizationID),
14231542
Subjects: &[]organization_management.Subject{
14241543
{
14251544
User: &organization_management.UserSubject{

castai/resource_omni_cluster.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func (r *omniClusterResource) Read(ctx context.Context, req resource.ReadRequest
124124
organizationID := state.OrganizationID.ValueString()
125125
clusterID := state.ID.ValueString()
126126

127-
apiResp, err := client.ClustersAPIGetClusterWithResponse(ctx, organizationID, clusterID)
127+
apiResp, err := client.ClustersAPIGetClusterWithResponse(ctx, organizationID, clusterID, nil)
128128
if err != nil {
129129
resp.Diagnostics.AddError("Failed to read omni cluster", err.Error())
130130
return

castai/resource_omni_cluster_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func testAccCheckOmniClusterDestroy(s *terraform.State) error {
4545
organizationID := rs.Primary.Attributes["organization_id"]
4646
clusterID := rs.Primary.ID
4747

48-
response, err := client.ClustersAPIGetClusterWithResponse(ctx, organizationID, clusterID)
48+
response, err := client.ClustersAPIGetClusterWithResponse(ctx, organizationID, clusterID, nil)
4949
if err != nil {
5050
return err
5151
}

castai/sdk/api.gen.go

Lines changed: 41 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

castai/sdk/omni/api.gen.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)