Skip to content

Commit

Permalink
feat(rbac): support resource names (#94)
Browse files Browse the repository at this point in the history
### Motivation

streamnative/unified-rbac#256


### Modification

- Support `ResourceNames`
- Upgrade the dependencies.
  • Loading branch information
mattisonchao authored Jan 16, 2025
1 parent 0b9f4ca commit fdcf35a
Show file tree
Hide file tree
Showing 10 changed files with 1,901 additions and 431 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/acctest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.22.4
id: go

- name: Setup Git token
Expand Down
91 changes: 85 additions & 6 deletions cloud/data_source_rolebinding.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/streamnative/cloud-api-server/pkg/apis/cloud/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
Expand Down Expand Up @@ -65,10 +66,59 @@ func dataSourceRoleBinding() *schema.Resource {
Type: schema.TypeString,
},
},
"cel": {
"condition_resource_names": {
Type: schema.TypeList,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names"],
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"organization": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_organization"],
},
"instance": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_instance"],
},
"cluster": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_cluster"],
},
"tenant": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_tenant"],
},
"namespace": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_namespace"],
},
"topic_domain": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_topic_domain"],
},
"topic_name": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_topic_name"],
},
"subscription": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_condition_resource_names_subscription"],
},
},
},
},
"condition_cel": {
Type: schema.TypeString,
Computed: true,
Description: descriptions["rolebinding_cel"],
Description: descriptions["rolebinding_condition_cel"],
},
},
}
Expand Down Expand Up @@ -122,10 +172,8 @@ func DataSourceRoleBindingRead(ctx context.Context, d *schema.ResourceData, meta
}
}

if roleBinding.Spec.CEL != nil {
if err = d.Set("cel", roleBinding.Spec.CEL); err != nil {
return diag.FromErr(fmt.Errorf("ERROR_SET_CEL: %w", err))
}
if err = conditionParse(organization, roleBinding, d); err != nil {
return diag.FromErr(fmt.Errorf("ERROR_SET_CONDITION: %w", err))
}

if len(roleBinding.Status.Conditions) >= 1 {
Expand All @@ -140,3 +188,34 @@ func DataSourceRoleBindingRead(ctx context.Context, d *schema.ResourceData, meta
d.SetId(fmt.Sprintf("%s/%s", roleBinding.Namespace, roleBinding.Name))
return nil
}

func conditionParse(organization string, binding *v1alpha1.RoleBinding, d *schema.ResourceData) error {
celExpression := binding.Spec.CEL
if celExpression != nil {
if err := d.Set("condition_cel", celExpression); err != nil {
return err
}
}

resourceNames := binding.Spec.ResourceNames
if resourceNames != nil {
var resourceNamesData []interface{}
for idx := range resourceNames {
resourceName := resourceNames[idx]
resourceNamesData = append(resourceNamesData, map[string]string{
"organization": organization,
"instance": resourceName.Instance,
"cluster": resourceName.Cluster,
"tenant": resourceName.Tenant,
"namespace": resourceName.Namespace,
"topic_domain": resourceName.TopicDomain,
"topic_name": resourceName.TopicName,
"subscription": resourceName.Subscription,
})
}
if err := d.Set("condition_resource_names", resourceNamesData); err != nil {
return err
}
}
return nil
}
29 changes: 19 additions & 10 deletions cloud/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,25 @@ func init() {
"default_gateway_private_service_ids": "The private service ids are ids are service names of PrivateLink in AWS, " +
"the ids of Private Service Attachment in GCP, " +
"and the aliases of PrivateLinkService in Azure.",
"oauth2_issuer_url": "The issuer url of the oauth2",
"oauth2_audience": "The audience of the oauth2",
"annotations": "The metadata annotations of the resource",
"rolebinding_ready": "The RoleBinding is ready, it will be set to 'True' after the cluster is ready",
"rolebinding_name": "The name of rolebinding",
"rolebinding_cluster_role_name": "The predefined role name",
"rolebinding_service_account_names": "The list of service accounts that are role binding names ",
"dns": "The DNS ID and name. Must specify together",
"rolebinding_cel": "The CEL(Common Expression Langauge) for conditional role binding",
"rolebinding_user_names": "The list of users that are role binding names ",
"oauth2_issuer_url": "The issuer url of the oauth2",
"oauth2_audience": "The audience of the oauth2",
"annotations": "The metadata annotations of the resource",
"rolebinding_ready": "The RoleBinding is ready, it will be set to 'True' after the cluster is ready",
"rolebinding_name": "The name of rolebinding",
"rolebinding_cluster_role_name": "The predefined role name",
"rolebinding_service_account_names": "The list of service accounts that are role binding names ",
"dns": "The DNS ID and name. Must specify together",
"rolebinding_user_names": "The list of users that are role binding names ",
"rolebinding_condition_cel": "The conditional role binding CEL(Common Expression Language) expression",
"rolebinding_condition_resource_names": "The list of conditional role binding resource names",
"rolebinding_condition_resource_names_organization": "The conditional role binding resource name - organization",
"rolebinding_condition_resource_names_instance": "The conditional role binding resource name - instance",
"rolebinding_condition_resource_names_cluster": "The conditional role binding resource name - cluster",
"rolebinding_condition_resource_names_tenant": "The conditional role binding resource name - tenant",
"rolebinding_condition_resource_names_namespace": "The conditional role binding resource name - namespace",
"rolebinding_condition_resource_names_topic_domain": "The conditional role binding resource name - topic domain(persistent/non-persistent)",
"rolebinding_condition_resource_names_topic_name": "The conditional role binding resource name - topic name",
"rolebinding_condition_resource_names_subscription": "The conditional role binding resource name - subscription",
}
}

Expand Down
109 changes: 99 additions & 10 deletions cloud/resource_rolebinding.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/streamnative/cloud-api-server/pkg/apis/cloud/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"strings"
"time"
)
Expand Down Expand Up @@ -91,10 +90,61 @@ func resourceRoleBinding() *schema.Resource {
Type: schema.TypeString,
},
},
"cel": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_cel"],
"condition_resource_names": {
ConflictsWith: []string{"condition_cel"},
Type: schema.TypeList,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names"],
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"organization": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_organization"],
},
"instance": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_instance"],
},
"cluster": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_cluster"],
},
"tenant": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_tenant"],
},
"namespace": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_namespace"],
},
"topic_domain": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_topic_domain"],
},
"topic_name": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_topic_name"],
},
"subscription": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_resource_names_subscription"],
},
},
},
},
"condition_cel": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["rolebinding_condition_cel"],
ConflictsWith: []string{"condition_resource_names"},
},
},
}
Expand All @@ -107,7 +157,6 @@ func resourceRoleBindingCreate(ctx context.Context, d *schema.ResourceData, m in
predefinedRoleName := d.Get("cluster_role_name").(string)
serviceAccountNames := d.Get("service_account_names").([]interface{})
userNames := d.Get("user_names").([]interface{})
cel := d.Get("cel").(string)

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

if cel != "" {
rb.Spec.CEL = pointer.String(cel)
}
conditionSet(namespace, d, rb)

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

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

roleBinding.Spec.Subjects = []v1alpha1.Subject{}

if serviceAccountNames != nil {
roleBinding.Spec.Subjects = []v1alpha1.Subject{}
for _, serviceAccountName := range serviceAccountNames {
roleBinding.Spec.Subjects = append(roleBinding.Spec.Subjects, v1alpha1.Subject{
APIGroup: "cloud.streamnative.io",
Expand All @@ -225,6 +274,17 @@ func resourceRoleBindingUpdate(ctx context.Context, d *schema.ResourceData, m in
})
}
}
if userNames != nil {
for _, userName := range userNames {
roleBinding.Spec.Subjects = append(roleBinding.Spec.Subjects, v1alpha1.Subject{
APIGroup: "cloud.streamnative.io",
Name: userName.(string),
Kind: "User",
})
}
}

conditionSet(namespace, d, roleBinding)
_, err = clientSet.CloudV1alpha1().RoleBindings(namespace).Update(ctx, roleBinding, metav1.UpdateOptions{})
if err != nil {
return diag.FromErr(fmt.Errorf("ERROR_UPDATE_ROLEBINDING: %w", err))
Expand Down Expand Up @@ -284,3 +344,32 @@ func resourceRoleBindingRead(ctx context.Context, d *schema.ResourceData, m inte
d.SetId(fmt.Sprintf("%s/%s", roleBinding.Namespace, roleBinding.Name))
return nil
}

func conditionSet(organization string, d *schema.ResourceData, binding *v1alpha1.RoleBinding) {
cel, exist := d.GetOk("condition_cel")
if exist {
celExpression := cel.(string)
binding.Spec.CEL = &celExpression
}

resourceNames := d.Get("condition_resource_names")
if resourceNames != nil {
var bindingResourceNames []v1alpha1.ResourceName
resourceNamesEntity := resourceNames.([]interface{})
for idx := range resourceNamesEntity {
resourceName := resourceNamesEntity[idx]
resourceElements := resourceName.(map[string]interface{})
bindingResourceNames = append(bindingResourceNames, v1alpha1.ResourceName{
Organization: organization,
Instance: resourceElements["instance"].(string),
Cluster: resourceElements["cluster"].(string),
Tenant: resourceElements["tenant"].(string),
Namespace: resourceElements["namespace"].(string),
TopicDomain: resourceElements["topic_domain"].(string),
TopicName: resourceElements["topic_name"].(string),
Subscription: resourceElements["subscription"].(string),
})
}
binding.Spec.ResourceNames = bindingResourceNames
}
}
42 changes: 42 additions & 0 deletions cloud/rolebinding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/streamnative/cloud-api-server/pkg/apis/cloud/v1alpha1"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -96,3 +98,43 @@ data "streamnative_rolebinding" "rolebinding_demo" {
},
})
}

func TestRoleBinding_ConditionParse(t *testing.T) {
orgName := "test"
requestResourceData := dataSourceRoleBinding().TestResourceData()
requestBinding := &v1alpha1.RoleBinding{
Spec: v1alpha1.RoleBindingSpec{
CEL: pointer.String("srn.instance == 'a'"),
},
}
err := conditionParse(orgName, requestBinding, requestResourceData)
assert.NoError(t, err)
expectRoleBinding := &v1alpha1.RoleBinding{}
conditionSet(orgName, requestResourceData, expectRoleBinding)
assert.Equal(t, expectRoleBinding.Spec, requestBinding.Spec)

requestResourceData = dataSourceRoleBinding().TestResourceData()
requestBinding = &v1alpha1.RoleBinding{
Spec: v1alpha1.RoleBindingSpec{
ResourceNames: []v1alpha1.ResourceName{
{
Organization: orgName,
Instance: "ins-1",
Cluster: "cluster-1",
Tenant: "tenant-1",
},
{
Organization: orgName,
Instance: "ins-2",
Cluster: "cluster-2",
Tenant: "tenant-2",
},
}},
}
err = conditionParse(orgName, requestBinding, requestResourceData)
assert.NoError(t, err)
expectRoleBinding = &v1alpha1.RoleBinding{}
conditionSet(orgName, requestResourceData, expectRoleBinding)
assert.Equal(t, expectRoleBinding.Spec, requestBinding.Spec)

}
Loading

0 comments on commit fdcf35a

Please sign in to comment.