Skip to content

Commit

Permalink
feat(lh-86966): add new Terraform resource to manage users in an MSP-…
Browse files Browse the repository at this point in the history
…managed tenant (#146)

* feat(lh-86966): add new Terraform resource to manage users in an MSP-managed tenant

This adds a new resource that allows an MSP portal super-admin to create users in a tenant managed
by the MSP portal.

* refactor(lh-86966): address Ido's comments

Also add validator for user role
  • Loading branch information
siddhuwarrier authored Oct 30, 2024
1 parent 0306b38 commit bf6e271
Show file tree
Hide file tree
Showing 24 changed files with 739 additions and 17 deletions.
9 changes: 9 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package client
import (
"context"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/msp/tenants"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/msp/users"
"net/http"

"github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector/connectoronboarding"
Expand Down Expand Up @@ -286,3 +287,11 @@ func (c *Client) ReadMspManagedTenantByUid(ctx context.Context, readByUidInput t
func (c *Client) FindMspManagedTenantByName(ctx context.Context, readByNameInput tenants.ReadByNameInput) (*tenants.MspTenantsOutput, error) {
return tenants.ReadByName(ctx, c.client, readByNameInput)
}

func (c *Client) CreateUsersInMspManagedTenant(ctx context.Context, createInput users.MspCreateUsersInput) (*[]users.UserDetails, *users.CreateError) {
return users.Create(ctx, c.client, createInput)
}

func (c *Client) DeleteUsersInMspManagedTenant(ctx context.Context, deleteInput users.MspDeleteUsersInput) (interface{}, error) {
return users.Delete(ctx, c.client, deleteInput)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package transactiontype
type Type string

const (
ONBOARD_ASA Type = "ONBOARD_ASA"
ONBOARD_IOS Type = "ONBOARD_IOS"
ONBOARD_DUO_ADMIN_PANEL Type = "ONBOARD_DUO_ADMIN_PANEL"
CREATE_FTD Type = "CREATE_FTD"
REGISTER_FTD Type = "REGISTER_FTD"
DELETE_CDFMC_MANAGED_FTD Type = "DELETE_CDFMC_MANAGED_FTD"
RECONNECT_ASA Type = "RECONNECT_ASA"
READ_ASA Type = "READ_ASA"
DEPLOY_ASA_DEVICE_CHANGES Type = "DEPLOY_ASA_DEVICE_CHANGES"
MSP_CREATE_TENANT Type = "MSP_CREATE_TENANT"
ONBOARD_ASA Type = "ONBOARD_ASA"
ONBOARD_IOS Type = "ONBOARD_IOS"
ONBOARD_DUO_ADMIN_PANEL Type = "ONBOARD_DUO_ADMIN_PANEL"
CREATE_FTD Type = "CREATE_FTD"
REGISTER_FTD Type = "REGISTER_FTD"
DELETE_CDFMC_MANAGED_FTD Type = "DELETE_CDFMC_MANAGED_FTD"
RECONNECT_ASA Type = "RECONNECT_ASA"
READ_ASA Type = "READ_ASA"
DEPLOY_ASA_DEVICE_CHANGES Type = "DEPLOY_ASA_DEVICE_CHANGES"
MSP_CREATE_TENANT Type = "MSP_CREATE_TENANT"
MSP_ADD_USERS_TO_TENANT Type = "MSP_ADD_USERS_TO_TENANT"
MSP_DELETE_USERS_FROM_TENANT Type = "MSP_DELETE_USERS_FROM_TENANT"
)
8 changes: 8 additions & 0 deletions client/internal/url/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,11 @@ func ReadMspManagedTenantByUid(baseUrl string, tenantUid string) string {
func FindMspManagedTenantsByName(baseUrl string, tenantName string) string {
return fmt.Sprintf("%s/api/rest/v1/msp/tenants?q=name:%s", baseUrl, tenantName)
}

func CreateUsersInMspManagedTenant(baseUrl string, tenantUid string) string {
return fmt.Sprintf("%s/api/rest/v1/msp/tenants/%s/users", baseUrl, tenantUid)
}

func DeleteUsersInMspManagedTenant(baseUrl string, tenantUid string) string {
return fmt.Sprintf("%s/api/rest/v1/msp/tenants/%s/users/delete", baseUrl, tenantUid)
}
5 changes: 5 additions & 0 deletions client/msp/users/constants_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package users_test

const (
baseUrl = "https://unittest.cdo.cisco.com"
)
40 changes: 40 additions & 0 deletions client/msp/users/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package users

import (
"context"
"fmt"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url"
)

func Create(ctx context.Context, client http.Client, createInp MspCreateUsersInput) (*[]UserDetails, *CreateError) {
client.Logger.Printf("Creating %d users in %s\n", len(createInp.Users), createInp.TenantUid)
createUrl := url.CreateUsersInMspManagedTenant(client.BaseUrl(), createInp.TenantUid)
transaction, err := publicapi.TriggerTransaction(
ctx,
client,
createUrl,
createInp,
)
if err != nil {
return nil, &CreateError{
Err: err,
CreatedResourceId: &transaction.EntityUid,
}
}
transaction, err = publicapi.WaitForTransactionToFinishWithDefaults(
ctx,
client,
transaction,
fmt.Sprintf("Waiting for users to be created and added to MSP-managed tenant %s...", createInp.TenantUid),
)
if err != nil {
return nil, &CreateError{
Err: err,
CreatedResourceId: &transaction.EntityUid,
}
}

return &createInp.Users, nil
}
165 changes: 165 additions & 0 deletions client/msp/users/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package users_test

import (
"context"
"fmt"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi/transaction"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi/transaction/transactionstatus"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi/transaction/transactiontype"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/user/auth/role"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/msp/users"
"github.com/google/uuid"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
netHttp "net/http"
"testing"
"time"
)

func TestCreate(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

t.Run("successfully create users in MSP-managed tenant", func(t *testing.T) {
httpmock.Reset()
var managedTenantUid = uuid.New().String()
var createInp = users.MspCreateUsersInput{
TenantUid: managedTenantUid,
Users: []users.UserDetails{
{Username: "[email protected]", Role: string(role.SuperAdmin), ApiOnlyUser: false},
{Username: "api-only-user", Role: string(role.ReadOnly), ApiOnlyUser: true},
},
}
var transactionUid = uuid.New().String()
var inProgressTransaction = transaction.Type{
TransactionUid: transactionUid,
TenantUid: uuid.New().String(),
EntityUid: managedTenantUid,
EntityUrl: "https://unittest.cdo.cisco.com/api/rest/v1/msp/tenants/" + managedTenantUid,
PollingUrl: "https://unittest.cdo.cisco.com/api/rest/v1/transactions/" + transactionUid,
SubmissionTime: "2024-09-10T20:10:00Z",
LastUpdatedTime: "2024-10-10T20:10:00Z",
Type: transactiontype.MSP_ADD_USERS_TO_TENANT,
Status: transactionstatus.IN_PROGRESS,
}
var doneTransaction = transaction.Type{
TransactionUid: transactionUid,
TenantUid: uuid.New().String(),
EntityUid: managedTenantUid,
EntityUrl: "https://unittest.cdo.cisco.com/api/rest/v1/msp/tenants/" + managedTenantUid,
PollingUrl: "https://unittest.cdo.cisco.com/api/rest/v1/transactions/" + transactionUid,
SubmissionTime: "2024-09-10T20:10:00Z",
LastUpdatedTime: "2024-10-10T20:10:00Z",
Type: transactiontype.MSP_ADD_USERS_TO_TENANT,
Status: transactionstatus.DONE,
}

httpmock.RegisterResponder(
netHttp.MethodPost,
fmt.Sprintf("/api/rest/v1/msp/tenants/%s/users", managedTenantUid),
httpmock.NewJsonResponderOrPanic(200, inProgressTransaction),
)
httpmock.RegisterResponder(
netHttp.MethodGet,
inProgressTransaction.PollingUrl,
httpmock.NewJsonResponderOrPanic(200, doneTransaction),
)

actual, err := users.Create(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), createInp)

assert.NotNil(t, actual, "Created users should have not been nil")
assert.Nil(t, err, "Created users operation should have not been an error")
assert.Equal(t, createInp.Users, *actual, "Created users operation should have been the same as the created tenant")
})

t.Run("user creation transaction fails", func(t *testing.T) {
httpmock.Reset()
var managedTenantUid = uuid.New().String()
var createInp = users.MspCreateUsersInput{
TenantUid: managedTenantUid,
Users: []users.UserDetails{
{Username: "[email protected]", Role: string(role.SuperAdmin), ApiOnlyUser: false},
{Username: "api-only-user", Role: string(role.ReadOnly), ApiOnlyUser: true},
},
}
var transactionUid = uuid.New().String()
var inProgressTransaction = transaction.Type{
TransactionUid: transactionUid,
TenantUid: uuid.New().String(),
EntityUid: managedTenantUid,
EntityUrl: "https://unittest.cdo.cisco.com/api/rest/v1/msp/tenants/" + managedTenantUid,
PollingUrl: "https://unittest.cdo.cisco.com/api/rest/v1/transactions/" + transactionUid,
SubmissionTime: "2024-09-10T20:10:00Z",
LastUpdatedTime: "2024-10-10T20:10:00Z",
Type: transactiontype.MSP_ADD_USERS_TO_TENANT,
Status: transactionstatus.IN_PROGRESS,
}
var errorTransaction = transaction.Type{
TransactionUid: transactionUid,
TenantUid: uuid.New().String(),
EntityUid: managedTenantUid,
EntityUrl: "https://unittest.cdo.cisco.com/api/rest/v1/msp/tenants/" + managedTenantUid,
PollingUrl: "https://unittest.cdo.cisco.com/api/rest/v1/transactions/" + transactionUid,
SubmissionTime: "2024-09-10T20:10:00Z",
LastUpdatedTime: "2024-10-10T20:10:00Z",
Type: transactiontype.MSP_ADD_USERS_TO_TENANT,
Status: transactionstatus.ERROR,
}

httpmock.RegisterResponder(
netHttp.MethodPost,
fmt.Sprintf("/api/rest/v1/msp/tenants/%s/users", managedTenantUid),
httpmock.NewJsonResponderOrPanic(200, inProgressTransaction),
)
httpmock.RegisterResponder(
netHttp.MethodGet,
inProgressTransaction.PollingUrl,
httpmock.NewJsonResponderOrPanic(200, errorTransaction),
)

actual, err := users.Create(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), createInp)

assert.Nil(t, actual, "Created users should be nil")
assert.NotNil(t, err, "Created users in tenant operation should have an error")
assert.Equal(t, users.CreateError{
Err: publicapi.NewTransactionErrorFromTransaction(errorTransaction),
CreatedResourceId: &managedTenantUid,
}, *err, "created transaction error does not match")
})

t.Run("user creation API call fails", func(t *testing.T) {
httpmock.Reset()
var managedTenantUid = uuid.New().String()
var createInp = users.MspCreateUsersInput{
TenantUid: managedTenantUid,
Users: []users.UserDetails{
{Username: "[email protected]", Role: string(role.SuperAdmin), ApiOnlyUser: false},
{Username: "api-only-user", Role: string(role.ReadOnly), ApiOnlyUser: true},
},
}
var transactionUid = uuid.New().String()
var errorTransaction = transaction.Type{
TransactionUid: transactionUid,
TenantUid: uuid.New().String(),
EntityUid: managedTenantUid,
EntityUrl: "https://unittest.cdo.cisco.com/api/rest/v1/msp/tenants/" + managedTenantUid,
PollingUrl: "https://unittest.cdo.cisco.com/api/rest/v1/transactions/" + transactionUid,
SubmissionTime: "2024-09-10T20:10:00Z",
LastUpdatedTime: "2024-10-10T20:10:00Z",
Type: transactiontype.MSP_ADD_USERS_TO_TENANT,
Status: transactionstatus.ERROR,
}

httpmock.RegisterResponder(
netHttp.MethodPost,
fmt.Sprintf("/api/rest/v1/msp/tenants/%s/users", managedTenantUid),
httpmock.NewJsonResponderOrPanic(200, errorTransaction),
)
actual, err := users.Create(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), createInp)

assert.Nil(t, actual, "Created users in tenant should have not been nil")
assert.NotNil(t, err, "Created users in tenant operation should have not been an error")
})
}
35 changes: 35 additions & 0 deletions client/msp/users/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package users

import (
"context"
"fmt"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url"
)

func Delete(ctx context.Context, client http.Client, deleteInp MspDeleteUsersInput) (interface{}, error) {
client.Logger.Printf("Deleting %d users in %s\n", len(deleteInp.Usernames), deleteInp.TenantUid)
deleteUrl := url.DeleteUsersInMspManagedTenant(client.BaseUrl(), deleteInp.TenantUid)
transaction, err := publicapi.TriggerTransaction(
ctx,
client,
deleteUrl,
deleteInp,
)
if err != nil {
return nil, err
}

transaction, err = publicapi.WaitForTransactionToFinishWithDefaults(
ctx,
client,
transaction,
fmt.Sprintf("Waiting for users to be deleted from MSP-managed tenant %s...", deleteInp.TenantUid),
)
if err != nil {
return nil, err
}

return nil, nil
}
Loading

0 comments on commit bf6e271

Please sign in to comment.