Skip to content

Commit

Permalink
feat: new mongodbatlas_stream_instance resource (#1685)
Browse files Browse the repository at this point in the history
* feat: new mongodbatlas_stream_instance resource implementation

* unit testing for sdk to TF model

* unit testing for tf to sdk models

* include acceptance tests

* add test case for potential empty fields

* resource documentation

* include example section

* configure acceptance tests in CI

* include missing step for terraform setup

* Update website/docs/r/stream_instance.html.markdown

Co-authored-by: Andrea Angiolillo <[email protected]>

* remove redundant return statement in import function

* addressing doc comments

---------

Co-authored-by: Andrea Angiolillo <[email protected]>
  • Loading branch information
AgustinBettati and andreaangiolillo authored Dec 5, 2023
1 parent a8dfd1b commit ebe95d4
Show file tree
Hide file tree
Showing 12 changed files with 690 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/acceptance-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
advanced_cluster: ${{ steps.filter.outputs.advanced_cluster }}
cluster: ${{ steps.filter.outputs.cluster }}
search_deployment: ${{ steps.filter.outputs.search_deployment }}
stream_instance: ${{ steps.filter.outputs.stream_instance }}
generic: ${{ steps.filter.outputs.generic }}
backup_online_archive: ${{ steps.filter.outputs.backup_online_archive }}
backup_snapshots: ${{ steps.filter.outputs.backup_snapshots }}
Expand Down Expand Up @@ -64,6 +65,8 @@ jobs:
- 'internal/service/cluster/*.go'
search_deployment:
- 'internal/service/searchdeployment/*.go'
stream_instance:
- 'internal/service/streaminstance/*.go'
generic:
- 'internal/service/backupcompliancepolicy/*.go'
- 'internal/service/auditing/*.go'
Expand Down Expand Up @@ -210,6 +213,30 @@ jobs:
TEST_REGEX: "^TestAccSearchDeployment"
run: make testacc

stream_instance:
needs: [ change-detection ]
if: ${{ needs.change-detection.outputs.stream_instance == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.event.label.name == 'run-testacc' || github.event.label.name == 'run-testacc-stream-instance' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version-file: 'go.mod'
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.terraform_version }}
terraform_wrapper: false
- name: Acceptance Tests
env:
MONGODB_ATLAS_PUBLIC_KEY: ${{ secrets.MONGODB_ATLAS_PUBLIC_KEY_CLOUD_DEV }}
MONGODB_ATLAS_PRIVATE_KEY: ${{ secrets.MONGODB_ATLAS_PRIVATE_KEY_CLOUD_DEV }}
MONGODB_ATLAS_ORG_ID: ${{ vars.MONGODB_ATLAS_ORG_ID_CLOUD_DEV }}
MONGODB_ATLAS_BASE_URL: ${{ vars.MONGODB_ATLAS_BASE_URL }}
TEST_REGEX: "^TestAccStreamInstance"
run: make testacc

generic: # Acceptance tests that do not use any time-consuming resource (example: cluster)
needs: [ change-detection ]
if: ${{ needs.change-detection.outputs.generic == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.event.label.name == 'run-testacc' || github.event.label.name == 'run-testacc-generic' }}
Expand Down
11 changes: 11 additions & 0 deletions examples/atlas-streams/stream-instance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# MongoDB Atlas Provider - Atlas Stream Instance defined in a Project

This example shows how to use Atlas Stream Instances in Terraform. It also creates a project, which is a prerequisite.

You must set the following variables:

- `public_key`: Atlas public key
- `private_key`: Atlas private key
- `org_id`: Unique 24-hexadecimal digit string that identifies the Organization that must contain the project.

To learn more, see the [Stream Instance Documentation](https://www.mongodb.com/docs/atlas/atlas-sp/manage-processing-instance/#configure-a-stream-processing-instance).
13 changes: 13 additions & 0 deletions examples/atlas-streams/stream-instance/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
resource "mongodbatlas_project" "example" {
name = "project-name"
org_id = var.org_id
}

resource "mongodbatlas_stream_instance" "example" {
project_id = mongodbatlas_project.example
instance_name = "InstanceName"
data_process_region = {
region = "VIRGINIA_USA"
cloud_provider = "AWS"
}
}
4 changes: 4 additions & 0 deletions examples/atlas-streams/stream-instance/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provider "mongodbatlas" {
public_key = var.public_key
private_key = var.private_key
}
12 changes: 12 additions & 0 deletions examples/atlas-streams/stream-instance/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
variable "public_key" {
description = "Public API key to authenticate to Atlas"
type = string
}
variable "private_key" {
description = "Private API key to authenticate to Atlas"
type = string
}
variable "org_id" {
description = "Unique 24-hexadecimal digit string that identifies your Atlas Organization"
type = string
}
9 changes: 9 additions & 0 deletions examples/atlas-streams/stream-instance/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
version = "~> 1.14"
}
}
required_version = ">= 1.0"
}
2 changes: 2 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/project"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/projectipaccesslist"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/searchdeployment"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/streaminstance"

"github.com/mongodb/terraform-provider-mongodbatlas/version"
)
Expand Down Expand Up @@ -423,6 +424,7 @@ func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resou
alertconfiguration.Resource,
projectipaccesslist.Resource,
searchdeployment.Resource,
streaminstance.Resource,
}
}

Expand Down
61 changes: 61 additions & 0 deletions internal/service/streaminstance/model_stream_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package streaminstance

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"go.mongodb.org/atlas-sdk/v20231115002/admin"
)

func NewStreamInstanceCreateReq(ctx context.Context, plan *TFStreamInstanceRSModel) (*admin.StreamsTenant, diag.Diagnostics) {
dataProcessRegion := &TFInstanceProcessRegionSpecModel{}
if diags := plan.DataProcessRegion.As(ctx, dataProcessRegion, basetypes.ObjectAsOptions{}); diags.HasError() {
return nil, diags
}
return &admin.StreamsTenant{
GroupId: plan.ProjectID.ValueStringPointer(),
Name: plan.InstanceName.ValueStringPointer(),
DataProcessRegion: &admin.StreamsDataProcessRegion{
CloudProvider: dataProcessRegion.CloudProvider.ValueString(),
Region: dataProcessRegion.Region.ValueString(),
},
}, nil
}

func NewStreamInstanceUpdateReq(ctx context.Context, plan *TFStreamInstanceRSModel) (*admin.StreamsDataProcessRegion, diag.Diagnostics) {
dataProcessRegion := &TFInstanceProcessRegionSpecModel{}
if diags := plan.DataProcessRegion.As(ctx, dataProcessRegion, basetypes.ObjectAsOptions{}); diags.HasError() {
return nil, diags
}
return &admin.StreamsDataProcessRegion{
CloudProvider: dataProcessRegion.CloudProvider.ValueString(),
Region: dataProcessRegion.Region.ValueString(),
}, nil
}

func NewTFStreamInstance(ctx context.Context, apiResp *admin.StreamsTenant) (*TFStreamInstanceRSModel, diag.Diagnostics) {
hostnames, diags := types.ListValueFrom(ctx, types.StringType, apiResp.Hostnames)

var dataProcessRegion = types.ObjectNull(ProcessRegionObjectType.AttrTypes)
if apiResp.DataProcessRegion != nil {
returnedProcessRegion, diagsProcessRegion := types.ObjectValueFrom(ctx, ProcessRegionObjectType.AttrTypes, TFInstanceProcessRegionSpecModel{
CloudProvider: types.StringValue(apiResp.DataProcessRegion.CloudProvider),
Region: types.StringValue(apiResp.DataProcessRegion.Region),
})
dataProcessRegion = returnedProcessRegion
diags.Append(diagsProcessRegion...)
}
if diags.HasError() {
return nil, diags
}

return &TFStreamInstanceRSModel{
ID: types.StringPointerValue(apiResp.Id),
InstanceName: types.StringPointerValue(apiResp.Name),
ProjectID: types.StringPointerValue(apiResp.GroupId),
DataProcessRegion: dataProcessRegion,
Hostnames: hostnames,
}, nil
}
174 changes: 174 additions & 0 deletions internal/service/streaminstance/model_stream_instance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package streaminstance_test

import (
"context"
"reflect"
"testing"

"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/streaminstance"
"go.mongodb.org/atlas-sdk/v20231115002/admin"
)

type sdkToTFModelTestCase struct {
SDKResp *admin.StreamsTenant
expectedTFModel *streaminstance.TFStreamInstanceRSModel
name string
}

const (
dummyProjectID = "111111111111111111111111"
dummyStreamInstanceID = "222222222222222222222222"
cloudProvider = "AWS"
region = "VIRGINIA_USA"
instanceName = "InstanceName"
)

var hostnames = []string{"atlas-stream.virginia-usa.a.query.mongodb-dev.net"}

func TestStreamInstanceSDKToTFModel(t *testing.T) {
testCases := []sdkToTFModelTestCase{
{
name: "Complete SDK response",
SDKResp: &admin.StreamsTenant{
Id: admin.PtrString(dummyStreamInstanceID),
DataProcessRegion: &admin.StreamsDataProcessRegion{
CloudProvider: cloudProvider,
Region: region,
},
GroupId: admin.PtrString(dummyProjectID),
Hostnames: hostnames,
Name: admin.PtrString(instanceName),
},
expectedTFModel: &streaminstance.TFStreamInstanceRSModel{
ID: types.StringValue(dummyStreamInstanceID),
DataProcessRegion: tfRegionObject(t, cloudProvider, region),
ProjectID: types.StringValue(dummyProjectID),
Hostnames: tfHostnamesList(t, hostnames),
InstanceName: types.StringValue(instanceName),
},
},
{
name: "Empty hostnames and dataProcessRegion in response", // should never happen, but verifying it is handled gracefully
SDKResp: &admin.StreamsTenant{
Id: admin.PtrString(dummyStreamInstanceID),
GroupId: admin.PtrString(dummyProjectID),
Name: admin.PtrString(instanceName),
},
expectedTFModel: &streaminstance.TFStreamInstanceRSModel{
ID: types.StringValue(dummyStreamInstanceID),
DataProcessRegion: types.ObjectNull(streaminstance.ProcessRegionObjectType.AttrTypes),
ProjectID: types.StringValue(dummyProjectID),
Hostnames: types.ListNull(types.StringType),
InstanceName: types.StringValue(instanceName),
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resultModel, diags := streaminstance.NewTFStreamInstance(context.Background(), tc.SDKResp)
if diags.HasError() {
t.Errorf("unexpected errors found: %s", diags.Errors()[0].Summary())
}
if !reflect.DeepEqual(resultModel, tc.expectedTFModel) {
t.Errorf("created terraform model did not match expected output")
}
})
}
}

type tfToSDKCreateModelTestCase struct {
tfModel *streaminstance.TFStreamInstanceRSModel
expectedSDKReq *admin.StreamsTenant
name string
}

func TestStreamInstanceTFToSDKCreateModel(t *testing.T) {
testCases := []tfToSDKCreateModelTestCase{
{
name: "Complete TF state",
tfModel: &streaminstance.TFStreamInstanceRSModel{
DataProcessRegion: tfRegionObject(t, cloudProvider, region),
ProjectID: types.StringValue(dummyProjectID),
InstanceName: types.StringValue(instanceName),
},
expectedSDKReq: &admin.StreamsTenant{
DataProcessRegion: &admin.StreamsDataProcessRegion{
CloudProvider: cloudProvider,
Region: region,
},
GroupId: admin.PtrString(dummyProjectID),
Name: admin.PtrString(instanceName),
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
apiReqResult, diags := streaminstance.NewStreamInstanceCreateReq(context.Background(), tc.tfModel)
if diags.HasError() {
t.Errorf("unexpected errors found: %s", diags.Errors()[0].Summary())
}
if !reflect.DeepEqual(apiReqResult, tc.expectedSDKReq) {
t.Errorf("created sdk model did not match expected output")
}
})
}
}

type tfToSDKUpdateModelTestCase struct {
tfModel *streaminstance.TFStreamInstanceRSModel
expectedSDKReq *admin.StreamsDataProcessRegion
name string
}

func TestStreamInstanceTFToSDKUpdateModel(t *testing.T) {
testCases := []tfToSDKUpdateModelTestCase{
{
name: "Complete TF state",
tfModel: &streaminstance.TFStreamInstanceRSModel{
ID: types.StringValue(dummyStreamInstanceID),
DataProcessRegion: tfRegionObject(t, cloudProvider, region),
ProjectID: types.StringValue(dummyProjectID),
Hostnames: tfHostnamesList(t, hostnames),
InstanceName: types.StringValue(instanceName),
},
expectedSDKReq: &admin.StreamsDataProcessRegion{
CloudProvider: cloudProvider,
Region: region,
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
apiReqResult, diags := streaminstance.NewStreamInstanceUpdateReq(context.Background(), tc.tfModel)
if diags.HasError() {
t.Errorf("unexpected errors found: %s", diags.Errors()[0].Summary())
}
if !reflect.DeepEqual(apiReqResult, tc.expectedSDKReq) {
t.Errorf("created sdk model did not match expected output")
}
})
}
}

func tfRegionObject(t *testing.T, cloudProvider, region string) types.Object {
dataProcessRegion, diags := types.ObjectValueFrom(context.Background(), streaminstance.ProcessRegionObjectType.AttrTypes, streaminstance.TFInstanceProcessRegionSpecModel{
CloudProvider: types.StringValue(cloudProvider),
Region: types.StringValue(region),
})
if diags.HasError() {
t.Errorf("failed to create terraform data process region model: %s", diags.Errors()[0].Summary())
}
return dataProcessRegion
}

func tfHostnamesList(t *testing.T, hostnames []string) types.List {
resultList, diags := types.ListValueFrom(context.Background(), types.StringType, hostnames)
if diags.HasError() {
t.Errorf("failed to create terraform hostnames list: %s", diags.Errors()[0].Summary())
}
return resultList
}
Loading

0 comments on commit ebe95d4

Please sign in to comment.