Skip to content

Commit

Permalink
feat(lh-86965): add msp_managed_tenant data source (#145)
Browse files Browse the repository at this point in the history
* feat(lh-86965): add msp_managed_tenant data source

Add a datasource to read an MSP managed tenant

* Run Acc test on branch, revert this before merge

* docs(lh-86965): add documentation

* fix

* refactor(lh-86965): address Tal's comments

* Fix

* chore(lh-86965): run acceptane test only on merge to main
  • Loading branch information
siddhuwarrier authored Oct 28, 2024
1 parent 8c66503 commit 0306b38
Show file tree
Hide file tree
Showing 24 changed files with 384 additions and 23 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ jobs:
- run: cat .github-action.env >> $GITHUB_ENV # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
- env:
TF_ACC: "1"
ACC_TEST_CISCO_CDO_MSP_API_TOKEN: ${{ secrets.ACC_TEST_CISCO_CDO_MSP_API_TOKEN }}
ACC_TEST_CISCO_CDO_API_TOKEN: ${{ secrets.ACC_TEST_CISCO_CDO_API_TOKEN }}
IOS_RESOURCE_PASSWORD: ${{ secrets.IOS_RESOURCE_PASSWORD }}
ASA_RESOURCE_SDC_PASSWORD: ${{ secrets.ASA_RESOURCE_SDC_PASSWORD }}
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,15 @@ ACC_TEST_CISCO_CDO_API_TOKEN=<CDO_API_TOKEN> make testacc
```

## Linting

Ensure you have golangci-lint installed:
```
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.61.0
```

Run following command in the `client` or `provider` directory.
```bash
golangci-lint run
~/go/bin/golangci-lint run #change ~/go/bin to your $GOBIN
```

## Running Examples
Expand Down
8 changes: 6 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ func (c *Client) CreateTenantUsingMspPortal(ctx context.Context, createInput ten
return tenants.Create(ctx, c.client, createInput)
}

func (c *Client) ReadMspManagedTenant(ctx context.Context, readByUidInput tenants.ReadByUidInput) (*tenants.MspTenantOutput, error) {
return tenants.Read(ctx, c.client, readByUidInput)
func (c *Client) ReadMspManagedTenantByUid(ctx context.Context, readByUidInput tenants.ReadByUidInput) (*tenants.MspTenantOutput, error) {
return tenants.ReadByUid(ctx, c.client, readByUidInput)
}

func (c *Client) FindMspManagedTenantByName(ctx context.Context, readByNameInput tenants.ReadByNameInput) (*tenants.MspTenantsOutput, error) {
return tenants.ReadByName(ctx, c.client, readByNameInput)
}
6 changes: 5 additions & 1 deletion client/internal/url/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ func CreateMspManagedTenant(baseUrl string) string {
return fmt.Sprintf("%s/api/rest/v1/msp/tenants/create", baseUrl)
}

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

func FindMspManagedTenantsByName(baseUrl string, tenantName string) string {
return fmt.Sprintf("%s/api/rest/v1/msp/tenants?q=name:%s", baseUrl, tenantName)
}
2 changes: 1 addition & 1 deletion client/msp/tenants/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func Create(ctx context.Context, client http.Client, createInp MspCreateTenantIn
}
}

readOut, err := Read(ctx, client, ReadByUidInput{Uid: transaction.EntityUid})
readOut, err := ReadByUid(ctx, client, ReadByUidInput{Uid: transaction.EntityUid})
client.Logger.Println("Created tenant for CDO")
if err == nil {
return readOut, nil
Expand Down
11 changes: 11 additions & 0 deletions client/msp/tenants/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ type MspTenantOutput struct {
Region string `json:"region"`
}

type MspTenantsOutput struct {
Count int `json:"count"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Items []MspTenantOutput `json:"items"`
}

type ReadByUidInput struct {
Uid string `json:"uid"`
}

type ReadByNameInput struct {
Name string `json:"name"`
}
17 changes: 15 additions & 2 deletions client/msp/tenants/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url"
)

func Read(ctx context.Context, client http.Client, readInp ReadByUidInput) (*MspTenantOutput, error) {
func ReadByUid(ctx context.Context, client http.Client, readInp ReadByUidInput) (*MspTenantOutput, error) {
client.Logger.Println("reading tenant by UID " + readInp.Uid)

readUrl := url.ReadMspManagedTenant(client.BaseUrl(), readInp.Uid)
readUrl := url.ReadMspManagedTenantByUid(client.BaseUrl(), readInp.Uid)
req := client.NewGet(ctx, readUrl)

var outp MspTenantOutput
Expand All @@ -19,3 +19,16 @@ func Read(ctx context.Context, client http.Client, readInp ReadByUidInput) (*Msp

return &outp, nil
}

func ReadByName(ctx context.Context, client http.Client, readInp ReadByNameInput) (*MspTenantsOutput, error) {
client.Logger.Println("reading tenant by name " + readInp.Name)
findByNameUrl := url.FindMspManagedTenantsByName(client.BaseUrl(), readInp.Name)
req := client.NewGet(ctx, findByNameUrl)

var outp MspTenantsOutput
if err := req.Send(&outp); err != nil {
return nil, err
}

return &outp, nil
}
58 changes: 56 additions & 2 deletions client/msp/tenants/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestRead(t *testing.T) {
"/api/rest/v1/msp/tenants/"+entityUid,
httpmock.NewJsonResponderOrPanic(200, tenantResponse),
)
actual, err := tenants.Read(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), tenants.ReadByUidInput{
actual, err := tenants.ReadByUid(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), tenants.ReadByUidInput{
Uid: entityUid,
})

Expand All @@ -46,11 +46,65 @@ func TestRead(t *testing.T) {
"/api/rest/v1/msp/tenants/"+entityUid,
httpmock.NewJsonResponderOrPanic(404, "Not found"),
)
actual, err := tenants.Read(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), tenants.ReadByUidInput{
actual, err := tenants.ReadByUid(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), tenants.ReadByUidInput{
Uid: entityUid,
})

assert.Nil(t, actual)
assert.ErrorContains(t, err, "Not Found")
})

t.Run("Find tenant by name", func(t *testing.T) {
httpmock.Reset()
var tenantName = "test-tenant"
var expectedTenant = tenants.MspTenantOutput{
Uid: uuid.New().String(),
Name: "test-tenant",
DisplayName: "Pineapple Crushers Inc",
Region: "STAGING",
}
var tenantResponse = tenants.MspTenantsOutput{
Count: 1,
Limit: 50,
Offset: 0,
Items: []tenants.MspTenantOutput{expectedTenant},
}

httpmock.RegisterResponder(
netHttp.MethodGet,
"/api/rest/v1/msp/tenants?q=name%3A"+tenantName,
httpmock.NewJsonResponderOrPanic(200, tenantResponse),
)
actual, err := tenants.ReadByName(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), tenants.ReadByNameInput{
Name: tenantName,
})

assert.NotNil(t, actual)
assert.Equal(t, *actual, tenantResponse)
assert.NoError(t, err)
})

t.Run("Find no tenants by name", func(t *testing.T) {
httpmock.Reset()
var tenantName = "test-tenant"
var tenantResponse = tenants.MspTenantsOutput{
Count: 0,
Limit: 50,
Offset: 0,
Items: []tenants.MspTenantOutput{},
}

httpmock.RegisterResponder(
netHttp.MethodGet,
"/api/rest/v1/msp/tenants?q=name%3A"+tenantName,
httpmock.NewJsonResponderOrPanic(200, tenantResponse),
)
actual, err := tenants.ReadByName(context.Background(), *http.MustNewWithConfig(baseUrl, "valid_token", 0, 0, time.Minute), tenants.ReadByNameInput{
Name: tenantName,
})

assert.NotNil(t, actual)
assert.Equal(t, *actual, tenantResponse)
assert.NoError(t, err)
})
}
2 changes: 1 addition & 1 deletion docs/data-sources/cdfmc.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ Use this data source to get information on the cloud-delivered FMC in your tenan
### Read-Only

- `domain_uuid` (String) The domain UUID of the cdFMC.
- `hostname` (String) Name of the tenant.
- `hostname` (String) Name of the cdFMC.
- `id` (String) Universally unique identifier for the cdFMC.
- `software_version` (String) Software version of the cdFMC.
26 changes: 26 additions & 0 deletions docs/data-sources/msp_managed_tenant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "cdo_msp_managed_tenant Data Source - cdo"
subcategory: ""
description: |-
Use this data source to get information on the cloud-delivered FMC in your tenant.
---

# cdo_msp_managed_tenant (Data Source)

Use this data source to get information on the cloud-delivered FMC in your tenant.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) Name of the tenant

### Read-Only

- `display_name` (String) Display name of the tenant
- `id` (String) Universally unique identifier of the tenant
- `region` (String) CDO region in which the tenant is created. This is the same region as the region of the MSP portal.
4 changes: 4 additions & 0 deletions provider/.github-action.env
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,8 @@ DUO_ADMIN_PANEL_RESOURCE_NEW_NAME=new-didi-duo
DUO_ADMIN_PANEL_RESOURCE_HOST=api-5ee7adc8.duosecurity.com
DUO_ADMIN_PANEL_RESOURCE_TAGS=tag1,tag2,tag3
TENANT_SETTINGS_TENANT_UID=d4200207-e3cc-4495-ad1e-e0bbedf44fe0
MSP_TENANT_NAME=CDO_terraform-provider-cdo
MSP_TENANT_DISPLAY_NAME=terraform-provider-cdo
MSP_TENANT_REGION=CI
MSP_TENANT_ID=ae98d25f-1089-4286-a3c5-505dcb4431a2
TF_LOG=DEBUG
1 change: 1 addition & 0 deletions provider/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ website/vendor

# Keep windows files with windows line endings
*.winfile eol=crlf
**/.terraform.lock.hcl
11 changes: 11 additions & 0 deletions provider/examples/data-sources/msp/tenants/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# MSP tenants Example

## Pre-requisites

You need access to an MSP Portal, and API token for the MSP portal.

## Usage
- Modify `terraform.tfvars` and `providers.tf` accordingly.
- Paste CDO API token for an MSP portal into `api_token.txt`
- see https://docs.defenseorchestrator.com/#!c-api-tokens.html for how to generate this.
- Specify the name of a tenant managed by the MSP Portal. You can get the tenant name by going to Settings in the MSP portal.
1 change: 1 addition & 0 deletions provider/examples/data-sources/msp/tenants/api_token.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Paste your API token here
7 changes: 7 additions & 0 deletions provider/examples/data-sources/msp/tenants/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
data "cdo_msp_managed_tenant" "tenant" {
name = "CDO_tenant-name"
}

output "tenant_display_name" {
value = data.cdo_msp_managed_tenant.tenant.display_name
}
12 changes: 12 additions & 0 deletions provider/examples/data-sources/msp/tenants/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
terraform {
required_providers {
cdo = {
source = "CiscoDevnet/cdo"
}
}
}

provider "cdo" {
base_url = "https://staging.dev.lockhart.io"
api_token = file("${path.module}/api_token.txt")
}
39 changes: 37 additions & 2 deletions provider/internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
)

const (
apiTokenEnvName = "ACC_TEST_CISCO_CDO_API_TOKEN"
apiTokenSecretName = "staging-terraform-provider-cdo-acceptance-test-api-token"
apiTokenEnvName = "ACC_TEST_CISCO_CDO_API_TOKEN"
mspApiTokenEnvName = "ACC_TEST_CISCO_CDO_MSP_API_TOKEN"
apiTokenSecretName = "staging-terraform-provider-cdo-acceptance-test-api-token"
mspApiTokenSecretName = "staging-terraform-provider-cdo-acceptance-test-api-token"
)

var cdoSecretManager = NewCdoSecretManager("us-west-2")
Expand All @@ -32,12 +34,30 @@ func GetApiToken() (string, error) {
return "", fmt.Errorf("failed to retrieve api token from environment variable and secret manager.\nenvironment variable name=%s\nsecret manager secret token name=%s\nplease set one of them.\ncause=%v", apiTokenEnvName, apiTokenSecretName, err)
}

func GetMspApiToken() (string, error) {
tokenFromEnv, ok := os.LookupEnv(mspApiTokenEnvName)
if ok {
return tokenFromEnv, nil
}

tokenFromSecretManager, err := cdoSecretManager.getCurrentSecretValue(mspApiTokenEnvName)
if err == nil {
return tokenFromSecretManager, nil
}

return "", fmt.Errorf("failed to retrieve api token from environment variable and secret manager.\nenvironment variable name=%s\nsecret manager secret token name=%s\nplease set one of them.\ncause=%v", mspApiTokenEnvName, mspApiTokenSecretName, err)
}

func PreCheckFunc(t *testing.T) func() {
return func() {
_, err := GetApiToken()
_, mspErr := GetMspApiToken()
if err != nil {
t.Fatalf("Precheck failed, cause=%v", err)
}
if mspErr != nil {
t.Fatalf("Precheck failed, cause=%v", mspErr)
}
}
}

Expand All @@ -56,6 +76,21 @@ func ProviderConfig() string {
`, token)
}

func MspProviderConfig() string {
mspToken, err := GetMspApiToken()
if err != nil {
panic(fmt.Errorf("failed to retrieve api token, cause=%w", err))
}

return fmt.Sprintf(`
provider "cdo" {
api_token = "%s"
base_url = "https://ci.manage.security.cisco.com"
}
// New line
`, mspToken)
}

// ProtoV6ProviderFactories are used to instantiate a provider during
// acceptance testing. The factory function will be invoked for every Terraform
// CLI command executed to create a provider server to which the CLI can
Expand Down
16 changes: 16 additions & 0 deletions provider/internal/acctest/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,22 @@ func (e *env) TenantSettingsTenantUid() string {
return e.mustGetString("TENANT_SETTINGS_TENANT_UID")
}

func (e *env) MspTenantName() string {
return e.mustGetString("MSP_TENANT_NAME")
}

func (e *env) MspTenantDisplayName() string {
return e.mustGetString("MSP_TENANT_DISPLAY_NAME")
}

func (e *env) MspTenantId() string {
return e.mustGetString("MSP_TENANT_ID")
}

func (e *env) MspTenantRegion() string {
return e.mustGetString("MSP_TENANT_REGION")
}

func (e *env) mustGetString(envName string) string {
value, ok := os.LookupEnv(envName)
if ok {
Expand Down
2 changes: 1 addition & 1 deletion provider/internal/cdfmc/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (d *DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, r
Computed: true,
},
"hostname": schema.StringAttribute{
MarkdownDescription: "Name of the tenant.",
MarkdownDescription: "Name of the cdFMC.",
Computed: true,
},
"software_version": schema.StringAttribute{
Expand Down
Loading

0 comments on commit 0306b38

Please sign in to comment.