diff --git a/client/v3/v3_service.go b/client/v3/v3_service.go index 3e7222c9a..3a0918413 100644 --- a/client/v3/v3_service.go +++ b/client/v3/v3_service.go @@ -72,11 +72,17 @@ type Service interface { UpdateProject(uuid string, body *Project) (*Project, error) DeleteProject(uuid string) error CreateAccessControlPolicy(request *AccessControlPolicy) (*AccessControlPolicy, error) - GetAccessControlPolicy(projectUUID string) (*AccessControlPolicy, error) + GetAccessControlPolicy(uuid string) (*AccessControlPolicy, error) ListAccessControlPolicy(getEntitiesRequest *DSMetadata) (*AccessControlPolicyListResponse, error) ListAllAccessControlPolicy(filter string) (*AccessControlPolicyListResponse, error) UpdateAccessControlPolicy(uuid string, body *AccessControlPolicy) (*AccessControlPolicy, error) DeleteAccessControlPolicy(uuid string) (*DeleteResponse, error) + CreateRole(request *Role) (*Role, error) + GetRole(uuid string) (*Role, error) + ListRole(getEntitiesRequest *DSMetadata) (*RoleListResponse, error) + ListAllRole(filter string) (*RoleListResponse, error) + UpdateRole(uuid string, body *Role) (*Role, error) + DeleteRole(uuid string) (*DeleteResponse, error) } /*CreateVM Creates a VM @@ -1447,3 +1453,148 @@ func (op Operations) DeleteAccessControlPolicy(uuid string) (*DeleteResponse, er return deleteResponse, op.client.Do(ctx, req, deleteResponse) } + +/*CreateRole creates a role + * This operation submits a request to create a role based on the input parameters. + * + * @param request *Role + * @return *Role + */ +func (op Operations) CreateRole(request *Role) (*Role, error) { + ctx := context.TODO() + + req, err := op.client.NewRequest(ctx, http.MethodPost, "/roles", request) + if err != nil { + return nil, err + } + + RoleResponse := new(Role) + + return RoleResponse, op.client.Do(ctx, req, RoleResponse) +} + +/*GetRole This operation gets a role. + * + * @param uuid The role uuid - string. + * @return *Role + */ +func (op Operations) GetRole(roleUUID string) (*Role, error) { + ctx := context.TODO() + + path := fmt.Sprintf("/roles/%s", roleUUID) + Role := new(Role) + + req, err := op.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, err + } + + return Role, op.client.Do(ctx, req, Role) +} + +/*ListRole gets a list of roles. + * + * @param metadata allows create filters to get specific data - *DSMetadata. + * @return *RoleListResponse + */ +func (op Operations) ListRole(getEntitiesRequest *DSMetadata) (*RoleListResponse, error) { + ctx := context.TODO() + path := "/roles/list" + + RoleList := new(RoleListResponse) + + req, err := op.client.NewRequest(ctx, http.MethodPost, path, getEntitiesRequest) + if err != nil { + return nil, err + } + + return RoleList, op.client.Do(ctx, req, RoleList) +} + +/*ListAllRole gets a list of Roles + * This operation gets a list of Roles, allowing for sorting and pagination. + * Note: Entities that have not been created successfully are not listed. + * @return *RoleListResponse + */ +func (op Operations) ListAllRole(filter string) (*RoleListResponse, error) { + entities := make([]*Role, 0) + + resp, err := op.ListRole(&DSMetadata{ + Filter: &filter, + Kind: utils.StringPtr("role"), + Length: utils.Int64Ptr(itemsPerPage), + }) + + if err != nil { + return nil, err + } + + totalEntities := utils.Int64Value(resp.Metadata.TotalMatches) + remaining := totalEntities + offset := utils.Int64Value(resp.Metadata.Offset) + + if totalEntities > itemsPerPage { + for hasNext(&remaining) { + resp, err = op.ListRole(&DSMetadata{ + Filter: &filter, + Kind: utils.StringPtr("role"), + Length: utils.Int64Ptr(itemsPerPage), + Offset: utils.Int64Ptr(offset), + }) + + if err != nil { + return nil, err + } + + entities = append(entities, resp.Entities...) + + offset += itemsPerPage + log.Printf("[Debug] total=%d, remaining=%d, offset=%d len(entities)=%d\n", totalEntities, remaining, offset, len(entities)) + } + + resp.Entities = entities + } + + return resp, nil +} + +/*UpdateRole Updates a role + * This operation submits a request to update a existing role based on the input parameters + * @param uuid The uuid of the entity - string. + * @param body - *Role + * @return *Role, error + */ +func (op Operations) UpdateRole(uuid string, body *Role) (*Role, error) { + ctx := context.TODO() + + path := fmt.Sprintf("/roles/%s", uuid) + RoleInput := new(Role) + + req, err := op.client.NewRequest(ctx, http.MethodPut, path, body) + if err != nil { + return nil, err + } + + return RoleInput, op.client.Do(ctx, req, RoleInput) +} + +/*DeleteRole Deletes a role + * This operation submits a request to delete a existing role. + * + * @param uuid The uuid of the entity. + * @return void + */ +func (op Operations) DeleteRole(uuid string) (*DeleteResponse, error) { + ctx := context.TODO() + + path := fmt.Sprintf("/roles/%s", uuid) + + req, err := op.client.NewRequest(ctx, http.MethodDelete, path, nil) + deleteResponse := new(DeleteResponse) + + if err != nil { + return nil, err + } + + return deleteResponse, op.client.Do(ctx, req, deleteResponse) +} diff --git a/client/v3/v3_service_test.go b/client/v3/v3_service_test.go index 45e0928cf..a652f2b7c 100644 --- a/client/v3/v3_service_test.go +++ b/client/v3/v3_service_test.go @@ -3766,3 +3766,431 @@ func TestOperations_DeleteAccessControlPolicy(t *testing.T) { }) } } + +func TestOperations_CreateRole(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/nutanix/v3/roles", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + expected := map[string]interface{}{ + "api_version": "3.1", + "metadata": map[string]interface{}{ + "name": "role_test_name", + "kind": "role", + "uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", + }, + "spec": map[string]interface{}{ + "resources": map[string]interface{}{ + "permission_reference_list": []interface{}{ + map[string]interface{}{ + "name": "role_test_name", + "kind": "role", + "uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", + }, + }, + }, + "name": "role_name", + "description": "description_test", + }, + } + + var v map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("decode json: %v", err) + } + + if !reflect.DeepEqual(v, expected) { + t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) + } + + fmt.Fprintf(w, `{ + "api_version": "3.1", + "metadata": { + "kind": "role", + "uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc" + } + }`) + }) + + type fields struct { + client *client.Client + } + + type args struct { + request *Role + } + + tests := []struct { + name string + fields fields + args args + want *Role + wantErr bool + }{ + { + "Test CreateRole", + fields{c}, + args{ + &Role{ + APIVersion: "3.1", + Metadata: &Metadata{ + Name: utils.StringPtr("role_test_name"), + Kind: utils.StringPtr("role"), + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + }, + Spec: &RoleSpec{ + Name: utils.StringPtr("role_name"), + Description: utils.StringPtr("description_test"), + Resources: &RoleResources{ + PermissionReferenceList: []*Reference{ + { + Kind: utils.StringPtr("role"), + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + Name: utils.StringPtr("role_test_name"), + }, + }, + }, + }, + }, + }, + &Role{ + APIVersion: "3.1", + Metadata: &Metadata{ + Kind: utils.StringPtr("role"), + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + }, + }, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.CreateRole(tt.args.request) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.CreateRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.CreateRole() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_GetRole(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/nutanix/v3/roles/cfde831a-4e87-4a75-960f-89b0148aa2cc", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + fmt.Fprint(w, `{"metadata": {"kind":"host","uuid":"cfde831a-4e87-4a75-960f-89b0148aa2cc"}}`) + }) + + hostResponse := &Role{} + hostResponse.Metadata = &Metadata{ + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + Kind: utils.StringPtr("host"), + } + + type fields struct { + client *client.Client + } + + type args struct { + UUID string + } + + tests := []struct { + name string + fields fields + args args + want *Role + wantErr bool + }{ + { + "Test GetRole OK", + fields{c}, + args{"cfde831a-4e87-4a75-960f-89b0148aa2cc"}, + hostResponse, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.GetRole(tt.args.UUID) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.GetRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.GetRole() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_ListRole(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/nutanix/v3/roles/list", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + fmt.Fprint(w, `{"entities":[{"metadata": {"kind":"host","uuid":"cfde831a-4e87-4a75-960f-89b0148aa2cc"}}]}`) + }) + + hostList := &RoleListResponse{} + hostList.Entities = make([]*Role, 1) + hostList.Entities[0] = &Role{} + hostList.Entities[0].Metadata = &Metadata{ + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + Kind: utils.StringPtr("host"), + } + + input := &DSMetadata{ + Length: utils.Int64Ptr(1.0), + } + + type fields struct { + client *client.Client + } + + type args struct { + getEntitiesRequest *DSMetadata + } + + tests := []struct { + name string + fields fields + args args + want *RoleListResponse + wantErr bool + }{ + { + "Test ListSubnet OK", + fields{c}, + args{input}, + hostList, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.ListRole(tt.args.getEntitiesRequest) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.ListRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.ListRole() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_UpdateRole(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/nutanix/v3/roles/cfde831a-4e87-4a75-960f-89b0148aa2cc", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPut) + + expected := map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "role_test_name", + "kind": "role", + "uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", + }, + "spec": map[string]interface{}{ + "resources": map[string]interface{}{ + "permission_reference_list": []interface{}{ + map[string]interface{}{ + "name": "role_test_name", + "kind": "role", + "uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", + }, + }, + }, + "name": "role_name", + "description": "description_test", + }, + } + + var v map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("decode json: %v", err) + } + + if !reflect.DeepEqual(v, expected) { + t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) + } + + fmt.Fprintf(w, `{ + "api_version": "3.1", + "metadata": { + "kind": "role", + "uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc" + } + }`) + }) + + type fields struct { + client *client.Client + } + + type args struct { + UUID string + body *Role + } + + tests := []struct { + name string + fields fields + args args + want *Role + wantErr bool + }{ + { + "Test CreateRole", + fields{c}, + args{ + "cfde831a-4e87-4a75-960f-89b0148aa2cc", + &Role{ + Metadata: &Metadata{ + Name: utils.StringPtr("role_test_name"), + Kind: utils.StringPtr("role"), + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + }, + Spec: &RoleSpec{ + Name: utils.StringPtr("role_name"), + Description: utils.StringPtr("description_test"), + Resources: &RoleResources{ + PermissionReferenceList: []*Reference{ + { + Kind: utils.StringPtr("role"), + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + Name: utils.StringPtr("role_test_name"), + }, + }, + }, + }, + }, + }, + &Role{ + APIVersion: "3.1", + Metadata: &Metadata{ + Kind: utils.StringPtr("role"), + UUID: utils.StringPtr("cfde831a-4e87-4a75-960f-89b0148aa2cc"), + }, + }, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.UpdateRole(tt.args.UUID, tt.args.body) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.UpdateRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.UpdateRole() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_DeleteRole(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/nutanix/v3/roles/cfde831a-4e87-4a75-960f-89b0148aa2cc", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodDelete) + + fmt.Fprintf(w, `{ + "status": { + "state": "DELETE_PENDING", + "execution_context": { + "task_uuid": "ff1b9547-dc9a-4ebd-a2ff-f2b718af935e" + } + }, + "spec": "", + "api_version": "3.1", + "metadata": { + "kind": "role", + "categories": { + "Project": "default" + } + } + }`) + }) + + type fields struct { + client *client.Client + } + + type args struct { + UUID string + } + + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + "Test DeleteRole OK", + fields{c}, + args{"cfde831a-4e87-4a75-960f-89b0148aa2cc"}, + false, + }, + + { + "Test DeleteRole Errored", + fields{c}, + args{}, + true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + if _, err := op.DeleteRole(tt.args.UUID); (err != nil) != tt.wantErr { + t.Errorf("Operations.DeleteRole() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/client/v3/v3_structs.go b/client/v3/v3_structs.go index cd08ab2ac..4d54cb059 100644 --- a/client/v3/v3_structs.go +++ b/client/v3/v3_structs.go @@ -2080,3 +2080,40 @@ type AccessControlPolicyListResponse struct { Entities []*AccessControlPolicy `json:"entities,omitempty"` Metadata *ListMetadataOutput `json:"metadata,omitempty"` } + +// RoleResources ... +type RoleResources struct { + PermissionReferenceList []*Reference `json:"permission_reference_list,omitempty"` +} + +// RoleStatus ... +type RoleStatus struct { + State *string `json:"state,omitempty"` + MessageList []*MessageResource `json:"message_list,omitempty"` + Name *string `json:"name,omitempty"` + Resources *RoleResources `json:"resources,omitempty"` + Description *string `json:"description,omitempty"` + ExecutionContext *ExecutionContext `json:"execution_context,omitempty"` +} + +// RoleSpec ... +type RoleSpec struct { + Name *string `json:"name,omitempty"` + Resources *RoleResources `json:"resources,omitempty"` + Description *string `json:"description,omitempty"` +} + +// Role Response object for intentful operations on a access policy +type Role struct { + Status *RoleStatus `json:"status,omitempty"` + Spec *RoleSpec `json:"spec,omitempty"` + APIVersion string `json:"api_version,omitempty"` + Metadata *Metadata `json:"metadata,omitempty"` +} + +// RoleListResponse Response object for intentful operation of access policy +type RoleListResponse struct { + APIVersion string `json:"api_version,omitempty"` + Entities []*Role `json:"entities,omitempty"` + Metadata *ListMetadataOutput `json:"metadata,omitempty"` +} diff --git a/go.sum b/go.sum index b06883903..11dc4ce94 100644 --- a/go.sum +++ b/go.sum @@ -7,44 +7,19 @@ cloud.google.com/go v0.45.1 h1:lRi0CHyU+ytlvylOlFKKq0af6JncuyoRh1J+QJBqQx0= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -github.com/Azure/azure-sdk-for-go v45.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= -github.com/ChrisTrenkamp/goxpath v0.0.0-20190607011252-c5096ec8773d/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw= -github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= -github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= @@ -100,6 +75,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= @@ -116,6 +92,7 @@ github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur9 github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0 h1:4DFWWMXVfbcN5So1sBNW9+yeiMqLFGl1wFLTL5R0Tgg= github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= @@ -129,10 +106,12 @@ github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQq github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -176,6 +155,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -186,6 +166,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= @@ -238,6 +219,7 @@ github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -251,6 +233,7 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= @@ -264,12 +247,15 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -325,12 +311,15 @@ github.com/nakabonne/nestif v0.3.0 h1:+yOViDGhg8ygGrmII72nV9B/zGxY188TYpfolntsaP github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= @@ -362,15 +351,20 @@ github.com/ryancurrah/gomodguard v1.0.2 h1:vumZpZardqQ9EfFIZDNEpKaMxfqqEBMhu0uSR github.com/ryancurrah/gomodguard v1.0.2/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83 h1:AtnWoOvTioyDXFvu96MWEeE8qj4COSQnJogzLy/u41A= github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= @@ -585,13 +579,16 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/nutanix/data_source_nutanix_access_control_policy_test.go b/nutanix/data_source_nutanix_access_control_policy_test.go index 968457b44..28cd4cdde 100644 --- a/nutanix/data_source_nutanix_access_control_policy_test.go +++ b/nutanix/data_source_nutanix_access_control_policy_test.go @@ -12,14 +12,13 @@ import ( func TestAccNutanixAccessControlPolicyDataSource_basic(t *testing.T) { name := acctest.RandomWithPrefix("accest-access-policy") description := "Description of my access control policy" - uuidRole := "760dac6c-be97-4b24-adb0-e3c3026dc8d5" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAccessControlPolicyDataSourceConfig(uuidRole, name, description), + Config: testAccAccessControlPolicyDataSourceConfig(name, description), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "data.nutanix_access_control_policy.test", "name", name), @@ -32,19 +31,27 @@ func TestAccNutanixAccessControlPolicyDataSource_basic(t *testing.T) { }) } -func testAccAccessControlPolicyDataSourceConfig(uuidRole, name, description string) string { +func testAccAccessControlPolicyDataSourceConfig(name, description string) string { return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "test role" + description = "description role" + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } +} resource "nutanix_access_control_policy" "test" { name = "%[1]s" description = "%[2]s" role_reference{ kind = "role" - uuid = "%[3]s" + uuid = nutanix_role.test.id } } data "nutanix_access_control_policy" "test" { access_control_policy_id = nutanix_access_control_policy.test.id } -`, name, description, uuidRole) +`, name, description) } diff --git a/nutanix/data_source_nutanix_role.go b/nutanix/data_source_nutanix_role.go new file mode 100644 index 000000000..790b0feef --- /dev/null +++ b/nutanix/data_source_nutanix_role.go @@ -0,0 +1,231 @@ +package nutanix + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + v3 "github.com/terraform-providers/terraform-provider-nutanix/client/v3" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func dataSourceNutanixRole() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNutanixRoleRead, + Schema: map[string]*schema.Schema{ + "role_id": { + Type: schema.TypeString, + Optional: true, + }, + "role_name": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"role_id"}, + }, + "api_version": { + Type: schema.TypeString, + Computed: true, + }, + "metadata": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "last_update_time": { + Type: schema.TypeString, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "creation_time": { + Type: schema.TypeString, + Computed: true, + }, + "spec_version": { + Type: schema.TypeString, + Computed: true, + }, + "spec_hash": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "categories": categoriesSchema(), + "owner_reference": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "project_reference": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "permission_reference_list": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceNutanixRoleRead(d *schema.ResourceData, meta interface{}) error { + // Get client connection + conn := meta.(*Client).API + + accessID, iok := d.GetOk("role_id") + roleName, rnOk := d.GetOk("role_name") + + if !iok || rnOk { + return fmt.Errorf("please provide `role_id` or `role_name`") + } + + var err error + var resp *v3.Role + + if iok { + resp, err = conn.V3.GetRole(accessID.(string)) + } + if rnOk { + resp, err = findRoleByName(conn, roleName.(string)) + } + + if err != nil { + return err + } + + m, c := setRSEntityMetadata(resp.Metadata) + + if err := d.Set("metadata", m); err != nil { + return err + } + if err := d.Set("categories", c); err != nil { + return err + } + if err := d.Set("project_reference", flattenReferenceValues(resp.Metadata.ProjectReference)); err != nil { + return err + } + if err := d.Set("owner_reference", flattenReferenceValues(resp.Metadata.OwnerReference)); err != nil { + return err + } + if err := d.Set("api_version", resp.APIVersion); err != nil { + return err + } + + if status := resp.Status; status != nil { + if err := d.Set("name", utils.StringValue(resp.Status.Name)); err != nil { + return err + } + if err := d.Set("description", utils.StringValue(resp.Status.Description)); err != nil { + return err + } + if err := d.Set("state", utils.StringValue(resp.Status.State)); err != nil { + return err + } + + if res := status.Resources; res != nil { + if err := d.Set("permission_reference_list", flattenArrayReferenceValues(status.Resources.PermissionReferenceList)); err != nil { + return err + } + } + } + d.SetId(utils.StringValue(resp.Metadata.UUID)) + + return nil +} + +func findRoleByName(conn *v3.Client, name string) (*v3.Role, error) { + filter := fmt.Sprintf("name==%s", name) + resp, err := conn.V3.ListAllRole(filter) + if err != nil { + return nil, err + } + + entities := resp.Entities + + found := make([]*v3.Role, 0) + for _, v := range entities { + if *v.Spec.Name == name { + found = append(found, v) + } + } + + if len(found) > 1 { + return nil, fmt.Errorf("your query returned more than one result. Please use role_id argument instead") + } + + if len(found) == 0 { + return nil, fmt.Errorf("role with the given name, not found") + } + + return found[0], nil +} diff --git a/nutanix/data_source_nutanix_role_test.go b/nutanix/data_source_nutanix_role_test.go new file mode 100644 index 000000000..573d6530c --- /dev/null +++ b/nutanix/data_source_nutanix_role_test.go @@ -0,0 +1,88 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccNutanixRoleDataSourceByID_basic(t *testing.T) { + name := acctest.RandomWithPrefix("accest-access-role") + description := "Description of my role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccRoleDataSourceConfigByID(name, description), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.nutanix_role.test", "name", name), + resource.TestCheckResourceAttr( + "data.nutanix_role.test", "description", description), + resource.TestCheckResourceAttrSet("data.nutanix_role.test", "name"), + ), + }, + }, + }) +} + +func TestAccNutanixRoleDataSourceByName_basic(t *testing.T) { + name := acctest.RandomWithPrefix("accest-access-role") + description := "Description of my role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccRoleDataSourceConfigByName(name, description), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.nutanix_role.test", "name", name), + resource.TestCheckResourceAttr( + "data.nutanix_role.test", "description", description), + resource.TestCheckResourceAttrSet("data.nutanix_role.test", "name"), + ), + }, + }, + }) +} + +func testAccRoleDataSourceConfigByID(name, description string) string { + return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "%[1]s" + description = "%[2]s" + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } +} + +data "nutanix_role" "test" { + role_id = nutanix_role.test.id +} +`, name, description) +} + +func testAccRoleDataSourceConfigByName(name, description string) string { + return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "%[1]s" + description = "%[2]s" + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } +} + +data "nutanix_role" "test" { + role_name = nutanix_role.test.name +} +`, name, description) +} diff --git a/nutanix/data_source_nutanix_roles.go b/nutanix/data_source_nutanix_roles.go new file mode 100644 index 000000000..f1f90566a --- /dev/null +++ b/nutanix/data_source_nutanix_roles.go @@ -0,0 +1,236 @@ +package nutanix + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + v3 "github.com/terraform-providers/terraform-provider-nutanix/client/v3" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func dataSourceNutanixRoles() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNutanixRolesRead, + Schema: map[string]*schema.Schema{ + "api_version": { + Type: schema.TypeString, + Computed: true, + }, + "entities": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "role_id": { + Type: schema.TypeString, + Required: true, + }, + "api_version": { + Type: schema.TypeString, + Computed: true, + }, + "metadata": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "last_update_time": { + Type: schema.TypeString, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "creation_time": { + Type: schema.TypeString, + Computed: true, + }, + "spec_version": { + Type: schema.TypeString, + Computed: true, + }, + "spec_hash": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "categories": categoriesSchema(), + "owner_reference": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "project_reference": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "permission_reference_list": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "metadata": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "sort_order": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "offset": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "length": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "sort_attribute": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceNutanixRolesRead(d *schema.ResourceData, meta interface{}) error { + // Get client connection + conn := meta.(*Client).API + req := &v3.DSMetadata{} + + metadata, filtersOk := d.GetOk("metadata") + if filtersOk { + req = buildDataSourceSubnetsMetadata(metadata.(*schema.Set)) + } + + resp, err := conn.V3.ListRole(req) + if err != nil { + return err + } + + if err := d.Set("api_version", resp.APIVersion); err != nil { + return err + } + + entities := make([]map[string]interface{}, len(resp.Entities)) + for k, v := range resp.Entities { + entity := make(map[string]interface{}) + + m, c := setRSEntityMetadata(v.Metadata) + + entity["metadata"] = m + entity["project_reference"] = flattenReferenceValues(v.Metadata.ProjectReference) + entity["owner_reference"] = flattenReferenceValues(v.Metadata.OwnerReference) + entity["categories"] = c + entity["api_version"] = v.APIVersion + + if status := v.Status; status != nil { + entity["name"] = utils.StringValue(v.Status.Name) + entity["description"] = utils.StringValue(v.Status.State) + entity["state"] = utils.StringValue(v.Status.State) + if res := status.Resources; res != nil { + entity["permission_reference_list"] = flattenArrayReferenceValues(status.Resources.PermissionReferenceList) + } + } + + entities[k] = entity + } + + if err := d.Set("entities", entities); err != nil { + return err + } + + d.SetId(resource.UniqueId()) + + return nil +} diff --git a/nutanix/data_source_nutanix_roles_test.go b/nutanix/data_source_nutanix_roles_test.go new file mode 100644 index 000000000..eeb64cde9 --- /dev/null +++ b/nutanix/data_source_nutanix_roles_test.go @@ -0,0 +1,25 @@ +package nutanix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccNutanixRolesDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccRolesDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_roles.test", "entities.0.name"), + ), + }, + }, + }) +} +func testAccRolesDataSourceConfig() string { + return `data "nutanix_roles" "test" {}` +} diff --git a/nutanix/provider.go b/nutanix/provider.go index 6728df3d9..eea4c33b7 100644 --- a/nutanix/provider.go +++ b/nutanix/provider.go @@ -99,6 +99,8 @@ func Provider() terraform.ResourceProvider { "nutanix_access_control_policies": dataSourceNutanixAccessControlPolicies(), "nutanix_project": dataSourceNutanixProject(), "nutanix_projects": dataSourceNutanixProjects(), + "nutanix_role": dataSourceNutanixRole(), + "nutanix_roles": dataSourceNutanixRoles(), }, ResourcesMap: map[string]*schema.Resource{ "nutanix_virtual_machine": resourceNutanixVirtualMachine(), @@ -109,6 +111,7 @@ func Provider() terraform.ResourceProvider { "nutanix_network_security_rule": resourceNutanixNetworkSecurityRule(), "nutanix_access_control_policy": resourceNutanixAccessControlPolicy(), "nutanix_project": resourceNutanixProject(), + "nutanix_role": resourceNutanixRole(), }, ConfigureFunc: providerConfigure, } diff --git a/nutanix/resource_nutanix_access_control_policy_test.go b/nutanix/resource_nutanix_access_control_policy_test.go index 6acfd7d1d..0db6642fc 100644 --- a/nutanix/resource_nutanix_access_control_policy_test.go +++ b/nutanix/resource_nutanix_access_control_policy_test.go @@ -17,7 +17,6 @@ const resourceAccessPolicy = "nutanix_access_control_policy.test" func TestAccNutanixAccessControlPolicy_basic(t *testing.T) { name := acctest.RandomWithPrefix("accest-access-policy") description := "Description of my access control policy" - uuidRole := "760dac6c-be97-4b24-adb0-e3c3026dc8d5" nameUpdated := acctest.RandomWithPrefix("accest-access-policy") descriptionUpdated := "Description of my access control policy updated" @@ -27,7 +26,7 @@ func TestAccNutanixAccessControlPolicy_basic(t *testing.T) { CheckDestroy: testAccCheckNutanixAccessControlPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccNutanixAccessControlPolicyConfig(uuidRole, name, description), + Config: testAccNutanixAccessControlPolicyConfig(name, description), Check: resource.ComposeTestCheckFunc( testAccCheckNutanixAccessControlPolicyExists(), resource.TestCheckResourceAttr(resourceAccessPolicy, "name", name), @@ -35,7 +34,7 @@ func TestAccNutanixAccessControlPolicy_basic(t *testing.T) { ), }, { - Config: testAccNutanixAccessControlPolicyConfig(uuidRole, nameUpdated, descriptionUpdated), + Config: testAccNutanixAccessControlPolicyConfig(nameUpdated, descriptionUpdated), Check: resource.ComposeTestCheckFunc( testAccCheckNutanixAccessControlPolicyExists(), resource.TestCheckResourceAttr(resourceAccessPolicy, "name", nameUpdated), @@ -54,7 +53,6 @@ func TestAccNutanixAccessControlPolicy_basic(t *testing.T) { func TestAccNutanixAccessControlPolicy_WithUser(t *testing.T) { name := acctest.RandomWithPrefix("accest-access-policy") description := "Description of my access control policy" - uuidRole := "760dac6c-be97-4b24-adb0-e3c3026dc8d5" nameUpdated := acctest.RandomWithPrefix("accest-access-policy") descriptionUpdated := "Description of my access control policy updated" @@ -64,7 +62,7 @@ func TestAccNutanixAccessControlPolicy_WithUser(t *testing.T) { CheckDestroy: testAccCheckNutanixAccessControlPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccNutanixAccessControlPolicyConfigWithUser(uuidRole, name, description), + Config: testAccNutanixAccessControlPolicyConfigWithUser(name, description), Check: resource.ComposeTestCheckFunc( testAccCheckNutanixAccessControlPolicyExists(), resource.TestCheckResourceAttr(resourceAccessPolicy, "name", name), @@ -72,7 +70,7 @@ func TestAccNutanixAccessControlPolicy_WithUser(t *testing.T) { ), }, { - Config: testAccNutanixAccessControlPolicyConfigWithUser(uuidRole, nameUpdated, descriptionUpdated), + Config: testAccNutanixAccessControlPolicyConfigWithUser(nameUpdated, descriptionUpdated), Check: resource.ComposeTestCheckFunc( testAccCheckNutanixAccessControlPolicyExists(), resource.TestCheckResourceAttr(resourceAccessPolicy, "name", nameUpdated), @@ -91,7 +89,6 @@ func TestAccNutanixAccessControlPolicy_WithUser(t *testing.T) { func TestAccNutanixAccessControlPolicy_WithCategory(t *testing.T) { name := acctest.RandomWithPrefix("accest-access-policy") description := "Description of my access control policy" - uuidRole := "760dac6c-be97-4b24-adb0-e3c3026dc8d5" nameUpdated := acctest.RandomWithPrefix("accest-access-policy") descriptionUpdated := "Description of my access control policy updated" @@ -101,7 +98,7 @@ func TestAccNutanixAccessControlPolicy_WithCategory(t *testing.T) { CheckDestroy: testAccCheckNutanixAccessControlPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccNutanixAccessControlPolicyConfigWithCategory(uuidRole, name, description, "Production"), + Config: testAccNutanixAccessControlPolicyConfigWithCategory(name, description, "Production"), Check: resource.ComposeTestCheckFunc( testAccCheckNutanixAccessControlPolicyExists(), testAccCheckNutanixCategories(resourceAccessPolicy), @@ -113,7 +110,7 @@ func TestAccNutanixAccessControlPolicy_WithCategory(t *testing.T) { ), }, { - Config: testAccNutanixAccessControlPolicyConfigWithCategory(uuidRole, nameUpdated, descriptionUpdated, "Staging"), + Config: testAccNutanixAccessControlPolicyConfigWithCategory(nameUpdated, descriptionUpdated, "Staging"), Check: resource.ComposeTestCheckFunc( testAccCheckNutanixAccessControlPolicyExists(), resource.TestCheckResourceAttr(resourceAccessPolicy, "categories.#", "1"), @@ -168,44 +165,68 @@ func testAccCheckNutanixAccessControlPolicyDestroy(s *terraform.State) error { return nil } -func testAccNutanixAccessControlPolicyConfig(uuidRole, name, description string) string { +func testAccNutanixAccessControlPolicyConfig(name, description string) string { return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "test role" + description = "description role" + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } +} resource "nutanix_access_control_policy" "test" { name = "%[1]s" description = "%[2]s" role_reference { kind = "role" - uuid = "%[3]s" + uuid = nutanix_role.test.id } } -`, name, description, uuidRole) +`, name, description) } -func testAccNutanixAccessControlPolicyConfigWithCategory(uuidRole, name, description, categoryValue string) string { +func testAccNutanixAccessControlPolicyConfigWithCategory(name, description, categoryValue string) string { return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "test role" + description = "description role" + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } +} resource "nutanix_access_control_policy" "test" { name = "%[1]s" description = "%[2]s" role_reference { kind = "role" - uuid = "%[3]s" + uuid = nutanix_role.test.id } categories { name = "Environment" - value = "%[4]s" + value = "%[3]s" } } -`, name, description, uuidRole, categoryValue) +`, name, description, categoryValue) } -func testAccNutanixAccessControlPolicyConfigWithUser(uuidRole, name, description string) string { +func testAccNutanixAccessControlPolicyConfigWithUser(name, description string) string { return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "test role" + description = "description role" + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } +} resource "nutanix_access_control_policy" "test" { name = "%[1]s" description = "%[2]s" role_reference { kind = "role" - uuid = "%[3]s" + uuid = nutanix_role.test.id } user_reference_list{ uuid = "00000000-0000-0000-0000-000000000000" @@ -274,5 +295,5 @@ resource "nutanix_access_control_policy" "test" { } } } -`, name, description, uuidRole) +`, name, description) } diff --git a/nutanix/resource_nutanix_role.go b/nutanix/resource_nutanix_role.go new file mode 100644 index 000000000..9ce1a8acc --- /dev/null +++ b/nutanix/resource_nutanix_role.go @@ -0,0 +1,405 @@ +package nutanix + +import ( + "bytes" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/spf13/cast" + v3 "github.com/terraform-providers/terraform-provider-nutanix/client/v3" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func resourceNutanixRole() *schema.Resource { + return &schema.Resource{ + Create: resourceNutanixRoleCreate, + Read: resourceNutanixRoleRead, + Update: resourceNutanixRoleUpdate, + Delete: resourceNutanixRoleDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "api_version": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "metadata": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "last_update_time": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "creation_time": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "spec_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "spec_hash": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + "categories": categoriesSchema(), + "owner_reference": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Optional: true, + }, + "uuid": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "project_reference": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uuid": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "kind": { + Type: schema.TypeString, + Optional: true, + Default: "project", + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "permission_reference_list": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Required: true, + }, + "uuid": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + Set: func(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["kind"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["uuid"].(string))) + return hashcode.String(buf.String()) + }, + }, + }, + } +} + +func resourceNutanixRoleCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Client).API + + request := &v3.Role{} + spec := &v3.RoleSpec{} + metadata := &v3.Metadata{} + role := &v3.RoleResources{} + + name, nameOk := d.GetOk("name") + permissions, permissionsOk := d.GetOk("permission_reference_list") + + if !nameOk && !permissionsOk { + return fmt.Errorf("please provide the required `name` and `permission_reference_list` attribute") + } + + if err := getMetadataAttributesV2(d, metadata, "role"); err != nil { + return err + } + + spec.Name = utils.StringPtr(name.(string)) + if desc, descOk := d.GetOk("description"); descOk { + spec.Description = utils.StringPtr(desc.(string)) + } + role.PermissionReferenceList = validateArrayRef(permissions.(*schema.Set), nil) + + if name, ok := d.GetOk("name"); ok { + spec.Name = utils.StringPtr(name.(string)) + } + spec.Resources = role + request.Metadata = metadata + request.Spec = spec + + resp, err := conn.V3.CreateRole(request) + if err != nil { + return fmt.Errorf("error creating Nutanix Role %s: %+v", utils.StringValue(spec.Name), err) + } + + taskUUID := resp.Status.ExecutionContext.TaskUUID.(string) + + // Wait for the Role to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING", "PENDING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshFunc(conn, taskUUID), + Timeout: subnetTimeout, + Delay: subnetDelay, + MinTimeout: subnetMinTimeout, + } + + if _, err := stateConf.WaitForState(); err != nil { + id := d.Id() + d.SetId("") + return fmt.Errorf("error waiting for role id (%s) to create: %+v", id, err) + } + + // Setting Description because in Get request is not present. + d.Set("description", utils.StringValue(resp.Spec.Description)) + + d.SetId(utils.StringValue(resp.Metadata.UUID)) + + return resourceNutanixRoleRead(d, meta) +} + +func resourceNutanixRoleRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Client).API + id := d.Id() + resp, err := conn.V3.GetRole(id) + if err != nil { + if strings.Contains(fmt.Sprint(err), "ENTITY_NOT_FOUND") { + d.SetId("") + return nil + } + errDel := resourceNutanixRoleDelete(d, meta) + if errDel != nil { + return fmt.Errorf("error deleting role (%s) after read error: %+v", id, errDel) + } + d.SetId("") + return fmt.Errorf("error reading role id (%s): %+v", id, err) + } + + m, c := setRSEntityMetadata(resp.Metadata) + + if err := d.Set("metadata", m); err != nil { + return err + } + if err := d.Set("categories", c); err != nil { + return err + } + if err := d.Set("project_reference", flattenReferenceValuesList(resp.Metadata.ProjectReference)); err != nil { + return err + } + if err := d.Set("owner_reference", flattenReferenceValuesList(resp.Metadata.OwnerReference)); err != nil { + return err + } + d.Set("api_version", resp.APIVersion) + + if status := resp.Status; status != nil { + if err := d.Set("name", utils.StringValue(resp.Status.Name)); err != nil { + return err + } + if err := d.Set("description", utils.StringValue(resp.Status.Description)); err != nil { + return err + } + if err := d.Set("state", utils.StringValue(resp.Status.State)); err != nil { + return err + } + + if res := status.Resources; res != nil { + if err := d.Set("permission_reference_list", flattenArrayReferenceValues(status.Resources.PermissionReferenceList)); err != nil { + return err + } + } + } + + return nil +} + +func resourceNutanixRoleUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Client).API + request := &v3.Role{} + metadata := &v3.Metadata{} + res := &v3.RoleResources{} + spec := &v3.RoleSpec{} + + id := d.Id() + response, err := conn.V3.GetRole(id) + + if err != nil { + if strings.Contains(fmt.Sprint(err), "ENTITY_NOT_FOUND") { + d.SetId("") + } + return fmt.Errorf("error retrieving for role id (%s) :%+v", id, err) + } + + if response.Metadata != nil { + metadata = response.Metadata + } + + if response.Spec != nil { + spec = response.Spec + + if response.Spec.Resources != nil { + res = response.Spec.Resources + } + } + + if d.HasChange("categories") { + metadata.Categories = expandCategories(d.Get("categories")) + } + if d.HasChange("owner_reference") { + metadata.OwnerReference = validateRefList(d.Get("owner_reference").([]interface{}), nil) + } + if d.HasChange("project_reference") { + metadata.ProjectReference = validateRefList(d.Get("project_reference").([]interface{}), utils.StringPtr("project")) + } + if d.HasChange("name") { + spec.Name = utils.StringPtr(d.Get("name").(string)) + } + if d.HasChange("description") { + spec.Description = utils.StringPtr(d.Get("description").(string)) + } + + if d.HasChange("permission_reference_list") { + res.PermissionReferenceList = validateArrayRef(d.Get("permission_reference_list").([]interface{}), nil) + } + + spec.Resources = res + request.Metadata = metadata + request.Spec = spec + + resp, errUpdate := conn.V3.UpdateRole(d.Id(), request) + if errUpdate != nil { + return fmt.Errorf("error updating role id %s): %s", d.Id(), errUpdate) + } + + taskUUID := resp.Status.ExecutionContext.TaskUUID.(string) + + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshFunc(conn, taskUUID), + Timeout: subnetTimeout, + Delay: subnetDelay, + MinTimeout: subnetMinTimeout, + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "error waiting for role (%s) to update: %s", d.Id(), err) + } + // Setting Description because in Get request is not present. + d.Set("description", utils.StringValue(resp.Spec.Description)) + + return resourceNutanixRoleRead(d, meta) +} + +func resourceNutanixRoleDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Client).API + + resp, err := conn.V3.DeleteRole(d.Id()) + if err != nil { + return fmt.Errorf("error deleting role id %s): %s", d.Id(), err) + } + + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING", "DELETED_PENDING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshFunc(conn, cast.ToString(resp.Status.ExecutionContext.TaskUUID)), + Timeout: subnetTimeout, + Delay: subnetDelay, + MinTimeout: subnetMinTimeout, + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "error waiting for role (%s) to update: %s", d.Id(), err) + } + + d.SetId("") + return nil +} + +func resourceNutanixRoleExists(conn *v3.Client, name string) (*string, error) { + var accessUUID *string + + filter := fmt.Sprintf("name==%s", name) + accessList, err := conn.V3.ListAllRole(filter) + + if err != nil { + return nil, err + } + + for _, access := range accessList.Entities { + if utils.StringValue(access.Status.Name) == name { + accessUUID = access.Metadata.UUID + } + } + return accessUUID, nil +} diff --git a/nutanix/resource_nutanix_role_test.go b/nutanix/resource_nutanix_role_test.go new file mode 100644 index 000000000..c5eebca98 --- /dev/null +++ b/nutanix/resource_nutanix_role_test.go @@ -0,0 +1,240 @@ +package nutanix + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +const resourceRole = "nutanix_role.test" + +func TestAccNutanixRole_basic(t *testing.T) { + name := acctest.RandomWithPrefix("accest-role") + description := "Description of my role" + nameUpdated := acctest.RandomWithPrefix("accest-role") + descriptionUpdated := "Description of my role updated" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixRoleConfig(name, description), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixRoleExists(), + resource.TestCheckResourceAttr(resourceRole, "name", name), + resource.TestCheckResourceAttr(resourceRole, "description", description), + ), + }, + { + Config: testAccNutanixRoleConfig(nameUpdated, descriptionUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixRoleExists(), + resource.TestCheckResourceAttr(resourceRole, "name", nameUpdated), + resource.TestCheckResourceAttr(resourceRole, "description", descriptionUpdated), + ), + }, + { + ResourceName: resourceRole, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNutanixRole_WithCategory(t *testing.T) { + name := acctest.RandomWithPrefix("accest-role") + description := "Description of my role" + nameUpdated := acctest.RandomWithPrefix("accest-role") + descriptionUpdated := "Description of my role updated" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixRoleConfigWithCategory(name, description, "Production"), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixRoleExists(), + testAccCheckNutanixCategories(resourceRole), + resource.TestCheckResourceAttr(resourceRole, "categories.#", "1"), + resource.TestCheckResourceAttrSet(resourceRole, "categories.2228745532.name"), + resource.TestCheckResourceAttrSet(resourceRole, "categories.2228745532.value"), + resource.TestCheckResourceAttr(resourceRole, "categories.2228745532.name", "Environment"), + resource.TestCheckResourceAttr(resourceRole, "categories.2228745532.value", "Production"), + ), + }, + { + Config: testAccNutanixRoleConfigWithCategory(nameUpdated, descriptionUpdated, "Staging"), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixRoleExists(), + resource.TestCheckResourceAttr(resourceRole, "categories.#", "1"), + resource.TestCheckResourceAttrSet(resourceRole, "categories.2940305446.name"), + resource.TestCheckResourceAttrSet(resourceRole, "categories.2940305446.value"), + resource.TestCheckResourceAttr(resourceRole, "categories.2940305446.name", "Environment"), + resource.TestCheckResourceAttr(resourceRole, "categories.2940305446.value", "Staging"), + ), + }, + { + ResourceName: resourceRole, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckNutanixRoleExists() resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceRole] + if !ok { + return fmt.Errorf("not found: %s", resourceRole) + } + + pretty, _ := json.MarshalIndent(rs, "", " ") + fmt.Print("\n\n[DEBUG] State of Role", string(pretty)) + + if rs.Primary.ID == "" { + return fmt.Errorf("no ID is set") + } + + return nil + } +} + +func testAccCheckNutanixRoleDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "nutanix_role" { + continue + } + if _, err := resourceNutanixRoleExists(conn.API, rs.Primary.ID); err != nil { + if strings.Contains(fmt.Sprint(err), "ENTITY_NOT_FOUND") { + return nil + } + return err + } + } + + return nil +} + +func testAccNutanixRoleConfig(name, description string) string { + return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "%[1]s" + description = "%[2]s" + permission_reference_list { + kind = "permission" + uuid = "d08ea95c-8221-4590-a77a-52d69639959a" + } + permission_reference_list { + kind = "permission" + uuid = "1a8a65c0-4333-42c6-9039-fd2585ceead7" + } + permission_reference_list { + kind = "permission" + uuid = "bea75573-e8fe-42a3-817a-bd1bd98ab110" + } + permission_reference_list { + kind = "permission" + uuid = "93e1cc93-d799-4f44-84ad-534814f6db0d" + } + permission_reference_list { + kind = "permission" + uuid = "62f53a1a-324c-4da6-bcb8-2cecc07b2cb7" + } + permission_reference_list { + kind = "permission" + uuid = "62f53a1a-324c-4da6-bcb8-2cecc07b2cb7" + } + permission_reference_list { + kind = "permission" + uuid = "ef38a553-a20f-4a2b-b12d-bb9cca03cbdd" + } + permission_reference_list { + kind = "permission" + uuid = "6e768a07-21ef-4615-84d0-7ec442ec942f" + } + permission_reference_list { + kind = "permission" + uuid = "91b77724-b163-473f-94a8-d016e75c18bd" + } + permission_reference_list { + kind = "permission" + uuid = "491ae1d0-5a8f-4bcc-9cee-068cd01c9274" + } + permission_reference_list { + kind = "permission" + uuid = "1dbfb7b4-9896-4c2a-b6fe-fbf113bae306" + } + permission_reference_list { + kind = "permission" + uuid = "740d29f7-18ae-4d07-aeef-3fc901c1887a" + } + permission_reference_list { + kind = "permission" + uuid = "b28f35be-6561-4a4a-9d90-a298d2de33d7" + } + permission_reference_list { + kind = "permission" + uuid = "428fad6c-8735-4a7d-bad3-8497bef051c8" + } + permission_reference_list { + kind = "permission" + uuid = "ea445ec5-f9bb-4af6-92e8-0d72d11ada85" + } + permission_reference_list { + kind = "permission" + uuid = "85a24ad8-67b6-4b63-b30f-96da1baca161" + } + permission_reference_list { + kind = "permission" + uuid = "d370823b-82d8-4518-a486-b75ba8e130d6" + } + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } + permission_reference_list { + kind = "permission" + uuid = "4e8e9007-8fbe-4709-a069-278259238e55" + } + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } +} +`, name, description) +} + +func testAccNutanixRoleConfigWithCategory(name, description, categoryValue string) string { + return fmt.Sprintf(` +resource "nutanix_role" "test" { + name = "%[1]s" + description = "%[2]s" + permission_reference_list { + kind = "permission" + uuid = "2e9988df-47ae-44ae-9114-ada346657b90" + } + categories { + name = "Environment" + value = "%[3]s" + } +} +`, name, description, categoryValue) +} diff --git a/website/docs/d/role.html.markdown b/website/docs/d/role.html.markdown new file mode 100644 index 000000000..7749617cc --- /dev/null +++ b/website/docs/d/role.html.markdown @@ -0,0 +1,85 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_role" +sidebar_current: "docs-nutanix-resource-role" +description: |- +Describes a Role +--- + +# nutanix_role + +Describes a Role. + +## Example Usage + +``` hcl +resource "nutanix_role" "test" { + name = "NAME" + description = "DESCRIPTION" + permission_reference_list { + kind = "permission" + uuid = "ID OF PERMISSION" + } + + data "nutanix_role" "test" { + role_id = nutanix_role.test.id + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `role_id`: - (Required) The UUID of a Role. + +## Attribute Reference + +The following attributes are exported: + +* `name`: - Name of the Role. +* `description`: - The description of the association of a role to a user in a given context. +* `categories`: - Categories for the Role. +* `project_reference`: - The reference to a project. +* `owner_reference`: - The reference to a user. +* `project_reference`: - The reference to a project. +* `permission_reference_list`: - (Required) List of permission references. + +## Attributes Reference + +The following attributes are exported: + +* `metadata`: - The vm kind metadata. +* `api_version` - The version of the API. +* `state`: - The state of the vm. + +### Metadata + +The metadata attribute exports the following: + +* `last_update_time`: - UTC date and time in RFC-3339 format when vm was last updated. +* `uuid`: - vm UUID. +* `creation_time`: - UTC date and time in RFC-3339 format when vm was created. +* `spec_version`: - Version number of the latest spec. +* `spec_hash`: - Hash of the spec. This will be returned from server. +* `name`: - vm name. +* `should_force_translate`: - Applied on Prism Central only. Indicate whether force to translate the spec of the fanout request to fit the target cluster API schema. + +### Categories + +The categories attribute supports the following: + +* `name`: - the key name. +* `value`: - value of the key. + +### Reference + +The `project_reference`, `owner_reference` attributes supports the following: + +* `kind`: - The kind name (Default value: project)(Required). +* `name`: - the name(Optional). +* `uuid`: - the UUID(Required). + +For `permission_reference_list` are the same as reference but used as array. + +See detailed information in [Nutanix Roles](https://www.nutanix.dev/reference/prism_central/v3/api/roles/). diff --git a/website/docs/d/roles.html.markdown b/website/docs/d/roles.html.markdown new file mode 100644 index 000000000..5eeb7c906 --- /dev/null +++ b/website/docs/d/roles.html.markdown @@ -0,0 +1,75 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_roles" +sidebar_current: "docs-nutanix-resource-roles" +description: |- + Describes a list of roles +--- + +# nutanix_roles + +Describes a list of roles. + +## Example Usage + +``` hcl +data "nutanix_roles" "test" {}} +``` + +## Attribute Reference + +The following attributes are exported: + +* `api_version`: version of the API +* `entities`: List of Projects + +# Entities + +The entities attribute element contains the followings attributes: + +* `name`: - Name of the Access Control Policy. +* `description`: - The description of the association of a role to a user in a given context. +* `categories`: - Categories for the Access Control Policy. +* `project_reference`: - The reference to a project. +* `owner_reference`: - The reference to a user. +* `project_reference`: - The reference to a project. +* `permission_reference_list`: - (Required) List of permission references. + +## Attributes Reference + +The following attributes are exported: + +* `metadata`: - The vm kind metadata. +* `api_version` - The version of the API. +* `state`: - The state of the vm. + +### Metadata + +The metadata attribute exports the following: + +* `last_update_time`: - UTC date and time in RFC-3339 format when vm was last updated. +* `uuid`: - vm UUID. +* `creation_time`: - UTC date and time in RFC-3339 format when vm was created. +* `spec_version`: - Version number of the latest spec. +* `spec_hash`: - Hash of the spec. This will be returned from server. +* `name`: - vm name. +* `should_force_translate`: - Applied on Prism Central only. Indicate whether force to translate the spec of the fanout request to fit the target cluster API schema. + +### Categories + +The categories attribute supports the following: + +* `name`: - the key name. +* `value`: - value of the key. + +### Reference + +The `project_reference`, `owner_reference` attributes supports the following: + +* `kind`: - The kind name (Default value: project)(Required). +* `name`: - the name(Optional). +* `uuid`: - the UUID(Required). + +For `permission_reference_list` are the same as reference but used as array. + +See detailed information in [Nutanix Roles](https://www.nutanix.dev/reference/prism_central/v3/api/roles/). diff --git a/website/docs/r/role.html.markdown b/website/docs/r/role.html.markdown new file mode 100644 index 000000000..608dfaf60 --- /dev/null +++ b/website/docs/r/role.html.markdown @@ -0,0 +1,83 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_role" +sidebar_current: "docs-nutanix-resource-role" +description: |- + This operation submits a request to create a role based on the input parameters. +--- + +# nutanix_role + +Provides a resource to create a role based on the input parameters. + +## Example Usage + +``` hcl +resource "nutanix_role" "test" { + name = "NAME" + description = "DESCRIPTION" + permission_reference_list { + kind = "permission" + uuid = "ID OF PERMISSION" + } + permission_reference_list { + kind = "permission" + uuid = "ID OF PERMISSION" + } + permission_reference_list { + kind = "permission" + uuid = "ID OF PERMISSION" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name`: - (Optional) Name of the role. +* `description`: - (Optional) The description of the association of a role to a user in a given context. +* `categories`: - (Optional) Categories for the role. +* `project_reference`: - (Optional) The reference to a project. +* `owner_reference`: - (Optional) The reference to a user. +* `project_reference`: - (Optional) The reference to a project. +* `permission_reference_list`: - (Required) List of permission references. + +## Attributes Reference + +The following attributes are exported: + +* `metadata`: - The vm kind metadata. +* `api_version` - The version of the API. +* `state`: - The state of the vm. + +### Metadata + +The metadata attribute exports the following: + +* `last_update_time`: - UTC date and time in RFC-3339 format when vm was last updated. +* `uuid`: - vm UUID. +* `creation_time`: - UTC date and time in RFC-3339 format when vm was created. +* `spec_version`: - Version number of the latest spec. +* `spec_hash`: - Hash of the spec. This will be returned from server. +* `name`: - vm name. +* `should_force_translate`: - Applied on Prism Central only. Indicate whether force to translate the spec of the fanout request to fit the target cluster API schema. + +### Categories + +The categories attribute supports the following: + +* `name`: - the key name. +* `value`: - value of the key. + +### Reference + +The `project_reference`, `owner_reference` attributes supports the following: + +* `kind`: - The kind name (Default value: project)(Required). +* `name`: - the name(Optional). +* `uuid`: - the UUID(Required). + +For `permission_reference_list` are the same as reference but used as array. + +See detailed information in [Nutanix Roles](https://www.nutanix.dev/reference/prism_central/v3/api/roles/).