Skip to content

Commit fdcf35a

Browse files
authored
feat(rbac): support resource names (#94)
### Motivation streamnative/unified-rbac#256 ### Modification - Support `ResourceNames` - Upgrade the dependencies.
1 parent 0b9f4ca commit fdcf35a

File tree

10 files changed

+1901
-431
lines changed

10 files changed

+1901
-431
lines changed

.github/workflows/acctest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
- name: Setup Go
3232
uses: actions/setup-go@v3
3333
with:
34-
go-version: 1.19
34+
go-version: 1.22.4
3535
id: go
3636

3737
- name: Setup Git token

cloud/data_source_rolebinding.go

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
77
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
"github.com/streamnative/cloud-api-server/pkg/apis/cloud/v1alpha1"
89
apierrors "k8s.io/apimachinery/pkg/api/errors"
910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1011
"strings"
@@ -65,10 +66,59 @@ func dataSourceRoleBinding() *schema.Resource {
6566
Type: schema.TypeString,
6667
},
6768
},
68-
"cel": {
69+
"condition_resource_names": {
70+
Type: schema.TypeList,
71+
Computed: true,
72+
Description: descriptions["rolebinding_condition_resource_names"],
73+
Elem: &schema.Resource{
74+
Schema: map[string]*schema.Schema{
75+
"organization": {
76+
Type: schema.TypeString,
77+
Computed: true,
78+
Description: descriptions["rolebinding_condition_resource_names_organization"],
79+
},
80+
"instance": {
81+
Type: schema.TypeString,
82+
Computed: true,
83+
Description: descriptions["rolebinding_condition_resource_names_instance"],
84+
},
85+
"cluster": {
86+
Type: schema.TypeString,
87+
Computed: true,
88+
Description: descriptions["rolebinding_condition_resource_names_cluster"],
89+
},
90+
"tenant": {
91+
Type: schema.TypeString,
92+
Computed: true,
93+
Description: descriptions["rolebinding_condition_resource_names_tenant"],
94+
},
95+
"namespace": {
96+
Type: schema.TypeString,
97+
Computed: true,
98+
Description: descriptions["rolebinding_condition_resource_names_namespace"],
99+
},
100+
"topic_domain": {
101+
Type: schema.TypeString,
102+
Computed: true,
103+
Description: descriptions["rolebinding_condition_resource_names_topic_domain"],
104+
},
105+
"topic_name": {
106+
Type: schema.TypeString,
107+
Computed: true,
108+
Description: descriptions["rolebinding_condition_resource_names_topic_name"],
109+
},
110+
"subscription": {
111+
Type: schema.TypeString,
112+
Computed: true,
113+
Description: descriptions["rolebinding_condition_resource_names_subscription"],
114+
},
115+
},
116+
},
117+
},
118+
"condition_cel": {
69119
Type: schema.TypeString,
70120
Computed: true,
71-
Description: descriptions["rolebinding_cel"],
121+
Description: descriptions["rolebinding_condition_cel"],
72122
},
73123
},
74124
}
@@ -122,10 +172,8 @@ func DataSourceRoleBindingRead(ctx context.Context, d *schema.ResourceData, meta
122172
}
123173
}
124174

125-
if roleBinding.Spec.CEL != nil {
126-
if err = d.Set("cel", roleBinding.Spec.CEL); err != nil {
127-
return diag.FromErr(fmt.Errorf("ERROR_SET_CEL: %w", err))
128-
}
175+
if err = conditionParse(organization, roleBinding, d); err != nil {
176+
return diag.FromErr(fmt.Errorf("ERROR_SET_CONDITION: %w", err))
129177
}
130178

131179
if len(roleBinding.Status.Conditions) >= 1 {
@@ -140,3 +188,34 @@ func DataSourceRoleBindingRead(ctx context.Context, d *schema.ResourceData, meta
140188
d.SetId(fmt.Sprintf("%s/%s", roleBinding.Namespace, roleBinding.Name))
141189
return nil
142190
}
191+
192+
func conditionParse(organization string, binding *v1alpha1.RoleBinding, d *schema.ResourceData) error {
193+
celExpression := binding.Spec.CEL
194+
if celExpression != nil {
195+
if err := d.Set("condition_cel", celExpression); err != nil {
196+
return err
197+
}
198+
}
199+
200+
resourceNames := binding.Spec.ResourceNames
201+
if resourceNames != nil {
202+
var resourceNamesData []interface{}
203+
for idx := range resourceNames {
204+
resourceName := resourceNames[idx]
205+
resourceNamesData = append(resourceNamesData, map[string]string{
206+
"organization": organization,
207+
"instance": resourceName.Instance,
208+
"cluster": resourceName.Cluster,
209+
"tenant": resourceName.Tenant,
210+
"namespace": resourceName.Namespace,
211+
"topic_domain": resourceName.TopicDomain,
212+
"topic_name": resourceName.TopicName,
213+
"subscription": resourceName.Subscription,
214+
})
215+
}
216+
if err := d.Set("condition_resource_names", resourceNamesData); err != nil {
217+
return err
218+
}
219+
}
220+
return nil
221+
}

cloud/provider.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,25 @@ func init() {
163163
"default_gateway_private_service_ids": "The private service ids are ids are service names of PrivateLink in AWS, " +
164164
"the ids of Private Service Attachment in GCP, " +
165165
"and the aliases of PrivateLinkService in Azure.",
166-
"oauth2_issuer_url": "The issuer url of the oauth2",
167-
"oauth2_audience": "The audience of the oauth2",
168-
"annotations": "The metadata annotations of the resource",
169-
"rolebinding_ready": "The RoleBinding is ready, it will be set to 'True' after the cluster is ready",
170-
"rolebinding_name": "The name of rolebinding",
171-
"rolebinding_cluster_role_name": "The predefined role name",
172-
"rolebinding_service_account_names": "The list of service accounts that are role binding names ",
173-
"dns": "The DNS ID and name. Must specify together",
174-
"rolebinding_cel": "The CEL(Common Expression Langauge) for conditional role binding",
175-
"rolebinding_user_names": "The list of users that are role binding names ",
166+
"oauth2_issuer_url": "The issuer url of the oauth2",
167+
"oauth2_audience": "The audience of the oauth2",
168+
"annotations": "The metadata annotations of the resource",
169+
"rolebinding_ready": "The RoleBinding is ready, it will be set to 'True' after the cluster is ready",
170+
"rolebinding_name": "The name of rolebinding",
171+
"rolebinding_cluster_role_name": "The predefined role name",
172+
"rolebinding_service_account_names": "The list of service accounts that are role binding names ",
173+
"dns": "The DNS ID and name. Must specify together",
174+
"rolebinding_user_names": "The list of users that are role binding names ",
175+
"rolebinding_condition_cel": "The conditional role binding CEL(Common Expression Language) expression",
176+
"rolebinding_condition_resource_names": "The list of conditional role binding resource names",
177+
"rolebinding_condition_resource_names_organization": "The conditional role binding resource name - organization",
178+
"rolebinding_condition_resource_names_instance": "The conditional role binding resource name - instance",
179+
"rolebinding_condition_resource_names_cluster": "The conditional role binding resource name - cluster",
180+
"rolebinding_condition_resource_names_tenant": "The conditional role binding resource name - tenant",
181+
"rolebinding_condition_resource_names_namespace": "The conditional role binding resource name - namespace",
182+
"rolebinding_condition_resource_names_topic_domain": "The conditional role binding resource name - topic domain(persistent/non-persistent)",
183+
"rolebinding_condition_resource_names_topic_name": "The conditional role binding resource name - topic name",
184+
"rolebinding_condition_resource_names_subscription": "The conditional role binding resource name - subscription",
176185
}
177186
}
178187

cloud/resource_rolebinding.go

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"github.com/streamnative/cloud-api-server/pkg/apis/cloud/v1alpha1"
1010
apierrors "k8s.io/apimachinery/pkg/api/errors"
1111
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12-
"k8s.io/utils/pointer"
1312
"strings"
1413
"time"
1514
)
@@ -91,10 +90,61 @@ func resourceRoleBinding() *schema.Resource {
9190
Type: schema.TypeString,
9291
},
9392
},
94-
"cel": {
95-
Type: schema.TypeString,
96-
Optional: true,
97-
Description: descriptions["rolebinding_cel"],
93+
"condition_resource_names": {
94+
ConflictsWith: []string{"condition_cel"},
95+
Type: schema.TypeList,
96+
Optional: true,
97+
Description: descriptions["rolebinding_condition_resource_names"],
98+
Elem: &schema.Resource{
99+
Schema: map[string]*schema.Schema{
100+
"organization": {
101+
Type: schema.TypeString,
102+
Optional: true,
103+
Description: descriptions["rolebinding_condition_resource_names_organization"],
104+
},
105+
"instance": {
106+
Type: schema.TypeString,
107+
Optional: true,
108+
Description: descriptions["rolebinding_condition_resource_names_instance"],
109+
},
110+
"cluster": {
111+
Type: schema.TypeString,
112+
Optional: true,
113+
Description: descriptions["rolebinding_condition_resource_names_cluster"],
114+
},
115+
"tenant": {
116+
Type: schema.TypeString,
117+
Optional: true,
118+
Description: descriptions["rolebinding_condition_resource_names_tenant"],
119+
},
120+
"namespace": {
121+
Type: schema.TypeString,
122+
Optional: true,
123+
Description: descriptions["rolebinding_condition_resource_names_namespace"],
124+
},
125+
"topic_domain": {
126+
Type: schema.TypeString,
127+
Optional: true,
128+
Description: descriptions["rolebinding_condition_resource_names_topic_domain"],
129+
},
130+
"topic_name": {
131+
Type: schema.TypeString,
132+
Optional: true,
133+
Description: descriptions["rolebinding_condition_resource_names_topic_name"],
134+
},
135+
"subscription": {
136+
Type: schema.TypeString,
137+
Optional: true,
138+
Description: descriptions["rolebinding_condition_resource_names_subscription"],
139+
},
140+
},
141+
},
142+
},
143+
"condition_cel": {
144+
Type: schema.TypeString,
145+
Optional: true,
146+
Description: descriptions["rolebinding_condition_cel"],
147+
ConflictsWith: []string{"condition_resource_names"},
98148
},
99149
},
100150
}
@@ -107,7 +157,6 @@ func resourceRoleBindingCreate(ctx context.Context, d *schema.ResourceData, m in
107157
predefinedRoleName := d.Get("cluster_role_name").(string)
108158
serviceAccountNames := d.Get("service_account_names").([]interface{})
109159
userNames := d.Get("user_names").([]interface{})
110-
cel := d.Get("cel").(string)
111160

112161
clientSet, err := getClientSet(getFactoryFromMeta(m))
113162
if err != nil {
@@ -154,9 +203,7 @@ func resourceRoleBindingCreate(ctx context.Context, d *schema.ResourceData, m in
154203
}
155204
}
156205

157-
if cel != "" {
158-
rb.Spec.CEL = pointer.String(cel)
159-
}
206+
conditionSet(namespace, d, rb)
160207

161208
if _, err := clientSet.CloudV1alpha1().RoleBindings(namespace).Create(ctx, rb, metav1.CreateOptions{
162209
FieldManager: "terraform-create",
@@ -204,6 +251,7 @@ func resourceRoleBindingDelete(ctx context.Context, d *schema.ResourceData, m in
204251
func resourceRoleBindingUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
205252
namespace := d.Get("organization").(string)
206253
name := d.Get("name").(string)
254+
userNames := d.Get("user_names").([]interface{})
207255
clientSet, err := getClientSet(getFactoryFromMeta(m))
208256
if err != nil {
209257
return diag.FromErr(fmt.Errorf("ERROR_INIT_CLIENT_ON_READ_ROLEBINDING: %w", err))
@@ -215,8 +263,9 @@ func resourceRoleBindingUpdate(ctx context.Context, d *schema.ResourceData, m in
215263

216264
serviceAccountNames := d.Get("service_account_names").([]interface{})
217265

266+
roleBinding.Spec.Subjects = []v1alpha1.Subject{}
267+
218268
if serviceAccountNames != nil {
219-
roleBinding.Spec.Subjects = []v1alpha1.Subject{}
220269
for _, serviceAccountName := range serviceAccountNames {
221270
roleBinding.Spec.Subjects = append(roleBinding.Spec.Subjects, v1alpha1.Subject{
222271
APIGroup: "cloud.streamnative.io",
@@ -225,6 +274,17 @@ func resourceRoleBindingUpdate(ctx context.Context, d *schema.ResourceData, m in
225274
})
226275
}
227276
}
277+
if userNames != nil {
278+
for _, userName := range userNames {
279+
roleBinding.Spec.Subjects = append(roleBinding.Spec.Subjects, v1alpha1.Subject{
280+
APIGroup: "cloud.streamnative.io",
281+
Name: userName.(string),
282+
Kind: "User",
283+
})
284+
}
285+
}
286+
287+
conditionSet(namespace, d, roleBinding)
228288
_, err = clientSet.CloudV1alpha1().RoleBindings(namespace).Update(ctx, roleBinding, metav1.UpdateOptions{})
229289
if err != nil {
230290
return diag.FromErr(fmt.Errorf("ERROR_UPDATE_ROLEBINDING: %w", err))
@@ -284,3 +344,32 @@ func resourceRoleBindingRead(ctx context.Context, d *schema.ResourceData, m inte
284344
d.SetId(fmt.Sprintf("%s/%s", roleBinding.Namespace, roleBinding.Name))
285345
return nil
286346
}
347+
348+
func conditionSet(organization string, d *schema.ResourceData, binding *v1alpha1.RoleBinding) {
349+
cel, exist := d.GetOk("condition_cel")
350+
if exist {
351+
celExpression := cel.(string)
352+
binding.Spec.CEL = &celExpression
353+
}
354+
355+
resourceNames := d.Get("condition_resource_names")
356+
if resourceNames != nil {
357+
var bindingResourceNames []v1alpha1.ResourceName
358+
resourceNamesEntity := resourceNames.([]interface{})
359+
for idx := range resourceNamesEntity {
360+
resourceName := resourceNamesEntity[idx]
361+
resourceElements := resourceName.(map[string]interface{})
362+
bindingResourceNames = append(bindingResourceNames, v1alpha1.ResourceName{
363+
Organization: organization,
364+
Instance: resourceElements["instance"].(string),
365+
Cluster: resourceElements["cluster"].(string),
366+
Tenant: resourceElements["tenant"].(string),
367+
Namespace: resourceElements["namespace"].(string),
368+
TopicDomain: resourceElements["topic_domain"].(string),
369+
TopicName: resourceElements["topic_name"].(string),
370+
Subscription: resourceElements["subscription"].(string),
371+
})
372+
}
373+
binding.Spec.ResourceNames = bindingResourceNames
374+
}
375+
}

cloud/rolebinding_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import (
66
"github.com/google/uuid"
77
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
88
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
9+
"github.com/streamnative/cloud-api-server/pkg/apis/cloud/v1alpha1"
910
"github.com/stretchr/testify/assert"
1011
"k8s.io/apimachinery/pkg/api/errors"
1112
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/utils/pointer"
1214
"strings"
1315
"testing"
1416
"time"
@@ -96,3 +98,43 @@ data "streamnative_rolebinding" "rolebinding_demo" {
9698
},
9799
})
98100
}
101+
102+
func TestRoleBinding_ConditionParse(t *testing.T) {
103+
orgName := "test"
104+
requestResourceData := dataSourceRoleBinding().TestResourceData()
105+
requestBinding := &v1alpha1.RoleBinding{
106+
Spec: v1alpha1.RoleBindingSpec{
107+
CEL: pointer.String("srn.instance == 'a'"),
108+
},
109+
}
110+
err := conditionParse(orgName, requestBinding, requestResourceData)
111+
assert.NoError(t, err)
112+
expectRoleBinding := &v1alpha1.RoleBinding{}
113+
conditionSet(orgName, requestResourceData, expectRoleBinding)
114+
assert.Equal(t, expectRoleBinding.Spec, requestBinding.Spec)
115+
116+
requestResourceData = dataSourceRoleBinding().TestResourceData()
117+
requestBinding = &v1alpha1.RoleBinding{
118+
Spec: v1alpha1.RoleBindingSpec{
119+
ResourceNames: []v1alpha1.ResourceName{
120+
{
121+
Organization: orgName,
122+
Instance: "ins-1",
123+
Cluster: "cluster-1",
124+
Tenant: "tenant-1",
125+
},
126+
{
127+
Organization: orgName,
128+
Instance: "ins-2",
129+
Cluster: "cluster-2",
130+
Tenant: "tenant-2",
131+
},
132+
}},
133+
}
134+
err = conditionParse(orgName, requestBinding, requestResourceData)
135+
assert.NoError(t, err)
136+
expectRoleBinding = &v1alpha1.RoleBinding{}
137+
conditionSet(orgName, requestResourceData, expectRoleBinding)
138+
assert.Equal(t, expectRoleBinding.Spec, requestBinding.Spec)
139+
140+
}

0 commit comments

Comments
 (0)