Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Creates TF models & interfaces for mongodbatlas_encryption_at_rest_private_endpoints plural data source #2502

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ func (p *MongodbtlasProvider) DataSources(context.Context) []func() datasource.D
}
previewDataSources := []func() datasource.DataSource{ // Data sources not yet in GA
encryptionatrestprivateendpoint.DataSource,
encryptionatrestprivateendpoint.PluralDataSource,
}
if providerEnablePreview {
dataSources = append(dataSources, previewDataSources...)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ func DataSourceSchema(ctx context.Context) schema.Schema {
Description: "Human-readable label that identifies the cloud provider of the private endpoint.",
MarkdownDescription: "Human-readable label that identifies the cloud provider of the private endpoint.",
},
"endpoint_id": schema.StringAttribute{
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not required, as an "id" attribute exists in the schema already

Required: true,
Description: "Unique 24-hexadecimal digit string that identifies the private endpoint.",
MarkdownDescription: "Unique 24-hexadecimal digit string that identifies the private endpoint.",
},
"error_message": schema.StringAttribute{
Computed: true,
Description: "Error message for failures associated with the Encryption At Rest private endpoint.",
Expand All @@ -30,7 +25,7 @@ func DataSourceSchema(ctx context.Context) schema.Schema {
MarkdownDescription: "Unique 24-hexadecimal digit string that identifies your project. Use the [/groups](#tag/Projects/operation/listProjects) endpoint to retrieve all projects to which the authenticated user has access.\n\n**NOTE**: Groups and projects are synonymous terms. Your group id is the same as your project id. For existing groups, your group/project id remains the same. The resource and corresponding endpoints use the term groups.",
},
"id": schema.StringAttribute{
Computed: true,
Required: true,
Description: "Unique 24-hexadecimal digit string that identifies the Private Endpoint Service.",
MarkdownDescription: "Unique 24-hexadecimal digit string that identifies the Private Endpoint Service.",
},
Expand Down
8 changes: 6 additions & 2 deletions internal/service/encryptionatrestprivateendpoint/model.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package encryptionatrestprivateendpoint

import (
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
"go.mongodb.org/atlas-sdk/v20240805001/admin"

"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
)

func NewTFEarPrivateEndpoint(apiResp *admin.EARPrivateEndpoint) *TFEarPrivateEndpointModel {
func NewTFEarPrivateEndpoint(apiResp *admin.EARPrivateEndpoint, projectID string) *TFEarPrivateEndpointModel {
if apiResp == nil {
return nil
}
return &TFEarPrivateEndpointModel{
ProjectID: types.StringValue(projectID),
CloudProvider: conversion.StringNullIfEmpty(apiResp.GetCloudProvider()),
ErrorMessage: conversion.StringNullIfEmpty(apiResp.GetErrorMessage()),
ID: conversion.StringNullIfEmpty(apiResp.GetId()),
Expand Down
71 changes: 68 additions & 3 deletions internal/service/encryptionatrestprivateendpoint/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ package encryptionatrestprivateendpoint_test
import (
"testing"

"go.mongodb.org/atlas-sdk/v20240805001/admin"

"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/encryptionatrestprivateendpoint"
"github.com/stretchr/testify/assert"
"go.mongodb.org/atlas-sdk/v20240805001/admin"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/encryptionatrestprivateendpoint"
)

const (
testCloudProvider = "AZURE"
testProjectID = "666666666067bd1e20a8bf14"
testTotalResultCount = 99
testErrMsg = "error occurred"
testID = "6666666999999adsfsgdg"
testRegionName = "US_EAST"
Expand Down Expand Up @@ -41,6 +45,7 @@ func TestEncryptionAtRestPrivateEndpointSDKToTFModel(t *testing.T) {
RegionName: types.StringValue(testRegionName),
Status: types.StringValue(testStatus),
PrivateEndpointConnectionName: types.StringValue(testPEConnectionName),
ProjectID: types.StringValue(testProjectID),
},
},
"Complete SDK response with error message": {
Expand All @@ -59,13 +64,14 @@ func TestEncryptionAtRestPrivateEndpointSDKToTFModel(t *testing.T) {
RegionName: types.StringValue(testRegionName),
Status: types.StringValue(testStatus),
PrivateEndpointConnectionName: types.StringValue(testPEConnectionName),
ProjectID: types.StringValue(testProjectID),
},
},
}

for testName, tc := range testCases {
t.Run(testName, func(t *testing.T) {
resultModel := encryptionatrestprivateendpoint.NewTFEarPrivateEndpoint(tc.SDKResp)
resultModel := encryptionatrestprivateendpoint.NewTFEarPrivateEndpoint(tc.SDKResp, testProjectID)
assert.Equal(t, tc.expectedTFModel, resultModel, "created terraform model did not match expected output")
})
}
Expand Down Expand Up @@ -119,3 +125,62 @@ func TestEncryptionAtRestPrivateEndpointTFModelToSDK(t *testing.T) {
})
}
}

type sdkToTFModelPluralDSTestCase struct {
expectedTFModel *encryptionatrestprivateendpoint.TFEncryptionAtRestPrivateEndpointsDSModel
SDKResp []admin.EARPrivateEndpoint
}

func TestEncryptionAtRestPrivateEndpointPluralDSSDKToTFModel(t *testing.T) {
testCases := map[string]sdkToTFModelPluralDSTestCase{
"Complete SDK response": {
SDKResp: []admin.EARPrivateEndpoint{
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we refactor the admin examples to helper functions. They seemed to be used in both TestEncryptionAtRestPrivateEndpointPluralDSSDKToTFModel and TestEncryptionAtRestPrivateEndpointTFModelToSDK?

CloudProvider: admin.PtrString(testCloudProvider),
ErrorMessage: admin.PtrString(""),
Id: admin.PtrString(testID),
RegionName: admin.PtrString(testRegionName),
Status: admin.PtrString(testStatus),
PrivateEndpointConnectionName: admin.PtrString(testPEConnectionName),
},
{
CloudProvider: admin.PtrString(testCloudProvider),
ErrorMessage: admin.PtrString(testErrMsg),
Id: admin.PtrString(testID),
RegionName: admin.PtrString(testRegionName),
Status: admin.PtrString(testStatus),
PrivateEndpointConnectionName: admin.PtrString(testPEConnectionName),
},
},
expectedTFModel: &encryptionatrestprivateendpoint.TFEncryptionAtRestPrivateEndpointsDSModel{
CloudProvider: types.StringValue(testCloudProvider),
ProjectID: types.StringValue(testProjectID),
Results: []encryptionatrestprivateendpoint.TFEarPrivateEndpointDSModel{
{
CloudProvider: types.StringValue(testCloudProvider),
ErrorMessage: types.StringNull(),
ID: types.StringValue(testID),
RegionName: types.StringValue(testRegionName),
Status: types.StringValue(testStatus),
PrivateEndpointConnectionName: types.StringValue(testPEConnectionName),
},
{
CloudProvider: types.StringValue(testCloudProvider),
ErrorMessage: types.StringValue(testErrMsg),
ID: types.StringValue(testID),
RegionName: types.StringValue(testRegionName),
Status: types.StringValue(testStatus),
PrivateEndpointConnectionName: types.StringValue(testPEConnectionName),
},
},
},
},
}

for testName, tc := range testCases {
t.Run(testName, func(t *testing.T) {
resultModel := encryptionatrestprivateendpoint.NewTFEarPrivateEndpoints(testProjectID, testCloudProvider, tc.SDKResp)
assert.Equal(t, tc.expectedTFModel, resultModel, "created terraform model did not match expected output")
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//nolint:gocritic
package encryptionatrestprivateendpoint

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
)

var _ datasource.DataSource = &encryptionAtRestPrivateEndpointsDS{}
var _ datasource.DataSourceWithConfigure = &encryptionAtRestPrivateEndpointsDS{}

func PluralDataSource() datasource.DataSource {
return &encryptionAtRestPrivateEndpointsDS{
DSCommon: config.DSCommon{
DataSourceName: fmt.Sprintf("%ss", encryptionAtRestPrivateEndpointName),
},
}
}

type encryptionAtRestPrivateEndpointsDS struct {
config.DSCommon
}

func (d *encryptionAtRestPrivateEndpointsDS) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = PluralDataSourceSchema(ctx)
}

func (d *encryptionAtRestPrivateEndpointsDS) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var encryptionAtRestPrivateEndpointsConfig TFEncryptionAtRestPrivateEndpointsDSModel
resp.Diagnostics.Append(req.Config.Get(ctx, &encryptionAtRestPrivateEndpointsConfig)...)
if resp.Diagnostics.HasError() {
return
}

// TODO: make get request to obtain list of results

// connV2 := d.Client.AtlasV2
// TODO: GetEncryptionAtRestPrivateEndpointsForCloudProvider returns paginated results
// v, resp, err := connV2.EncryptionAtRestUsingCustomerKeyManagementApi.GetEncryptionAtRestPrivateEndpointsForCloudProvider(ctx, "", "").Execute()
// if err != nil {
// resp.Diagnostics.AddError("error fetching results", err.Error())
// return
//}

// TODO: process response into new terraform state
// newEncryptionAtRestPrivateEndpointsModel := NewTFEarPrivateEndpoints("","",conversion.IntPtr(99), v.GetResults())
// if diags.HasError() {
// resp.Diagnostics.Append(diags...)
// return
// }
// resp.Diagnostics.Append(resp.State.Set(ctx, NewTFEarPrivateEndpoints(ctx))...)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package encryptionatrestprivateendpoint

import (
"context"

"go.mongodb.org/atlas-sdk/v20240805001/admin"

"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
)

func PluralDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"cloud_provider": schema.StringAttribute{
Required: true,
Description: "Human-readable label that identifies the cloud provider for the private endpoints to return.",
MarkdownDescription: "Human-readable label that identifies the cloud provider for the private endpoints to return.",
},
"project_id": schema.StringAttribute{
Required: true,
Description: "Unique 24-hexadecimal digit string that identifies your project. Use the [/groups](#tag/Projects/operation/listProjects) endpoint to retrieve all projects to which the authenticated user has access.\n\n**NOTE**: Groups and projects are synonymous terms. Your group id is the same as your project id. For existing groups, your group/project id remains the same. The resource and corresponding endpoints use the term groups.",
MarkdownDescription: "Unique 24-hexadecimal digit string that identifies your project. Use the [/groups](#tag/Projects/operation/listProjects) endpoint to retrieve all projects to which the authenticated user has access.\n\n**NOTE**: Groups and projects are synonymous terms. Your group id is the same as your project id. For existing groups, your group/project id remains the same. The resource and corresponding endpoints use the term groups.",
},
Copy link
Collaborator

@EspenAlbert EspenAlbert Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you seen the PaginatedDSSchema ? I think that could apply for this resource
*UPDATE: I was taking an extra look. It looks like pagination is not supported on that endpoint

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would double check in the mms implementation if effectively pageNum and itemsPerPage are not defined, or its a miss of not adding correct annotation for api spec. Seems strange since totalCount is being returned.

Copy link
Collaborator Author

@maastha maastha Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently not supported in the API/MMS, double-checking with the API team if this is expected, awaiting a response from them

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checked with the team, they have updated the API and those attributes are now supported so I'll update the schema accordingly.

Regarding use of PaginatedDSSchema I understand it may make the code a bit modular but since these schemas are being generated using scaffolding, but I don't see much value in making such changes to the generated schema and I'd like to keep the generated schema as close to what is auto-generated as possible.

If we have a strong opinion on wanting to use functions like PaginatedDSSchema, I think we should work on including it in our auto-generation workflow first.

@AgustinBettati @EspenAlbert lmk if you guys disagree

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will update this thread after the weekly meeting today

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated PR to not support pagination as discussed today
cc @AgustinBettati

"results": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"cloud_provider": schema.StringAttribute{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we reuse the data_source schema similar to stream-connection DS or do we prefer to keep them independent?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's minor difference like presence of project_id in the singular DS model so they aren't exactly the same, I'd prefer to keep the autogenerated schema code close to the API implementation.

Computed: true,
Description: "Human-readable label that identifies the cloud provider for the Encryption At Rest private endpoint.",
MarkdownDescription: "Human-readable label that identifies the cloud provider for the Encryption At Rest private endpoint.",
},
"error_message": schema.StringAttribute{
Computed: true,
Description: "Error message for failures associated with the Encryption At Rest private endpoint.",
MarkdownDescription: "Error message for failures associated with the Encryption At Rest private endpoint.",
},
"id": schema.StringAttribute{
Computed: true,
Description: "Unique 24-hexadecimal digit string that identifies the Private Endpoint Service.",
MarkdownDescription: "Unique 24-hexadecimal digit string that identifies the Private Endpoint Service.",
},
"private_endpoint_connection_name": schema.StringAttribute{
Computed: true,
Description: "Connection name of the Azure Private Endpoint.",
MarkdownDescription: "Connection name of the Azure Private Endpoint.",
},
"region_name": schema.StringAttribute{
Computed: true,
Description: "Cloud provider region in which the Encryption At Rest private endpoint is located.",
MarkdownDescription: "Cloud provider region in which the Encryption At Rest private endpoint is located.",
},
"status": schema.StringAttribute{
Computed: true,
Description: "State of the Encryption At Rest private endpoint.",
MarkdownDescription: "State of the Encryption At Rest private endpoint.",
},
},
},
Computed: true,
Description: "List of returned documents that MongoDB Cloud providers when completing this request.",
MarkdownDescription: "List of returned documents that MongoDB Cloud providers when completing this request.",
},
},
}
}

type TFEncryptionAtRestPrivateEndpointsDSModel struct {
CloudProvider types.String `tfsdk:"cloud_provider"`
ProjectID types.String `tfsdk:"project_id"`
Results []TFEarPrivateEndpointDSModel `tfsdk:"results"`
}

type TFEarPrivateEndpointDSModel struct {
CloudProvider types.String `tfsdk:"cloud_provider"`
ErrorMessage types.String `tfsdk:"error_message"`
ID types.String `tfsdk:"id"`
PrivateEndpointConnectionName types.String `tfsdk:"private_endpoint_connection_name"`
RegionName types.String `tfsdk:"region_name"`
Status types.String `tfsdk:"status"`
}

func NewTFEarPrivateEndpoints(projectID, cloudProvider string, results []admin.EARPrivateEndpoint) *TFEncryptionAtRestPrivateEndpointsDSModel {
return &TFEncryptionAtRestPrivateEndpointsDSModel{
ProjectID: types.StringValue(projectID),
CloudProvider: types.StringValue(cloudProvider),
Results: NewTFEarPrivateEndpointsDS(results),
}
}

func NewTFEarPrivateEndpointsDS(endpoints []admin.EARPrivateEndpoint) []TFEarPrivateEndpointDSModel {
results := make([]TFEarPrivateEndpointDSModel, len(endpoints))

for i, v := range endpoints {
results[i] = TFEarPrivateEndpointDSModel{
CloudProvider: conversion.StringNullIfEmpty(v.GetCloudProvider()),
ErrorMessage: conversion.StringNullIfEmpty(v.GetErrorMessage()),
ID: conversion.StringNullIfEmpty(v.GetId()),
RegionName: conversion.StringNullIfEmpty(v.GetRegionName()),
Status: conversion.StringNullIfEmpty(v.GetStatus()),
PrivateEndpointConnectionName: conversion.StringNullIfEmpty(v.GetPrivateEndpointConnectionName()),
}
}
return results
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ func ResourceSchema(ctx context.Context) schema.Schema {
Description: "Human-readable label that identifies the cloud provider for the Encryption At Rest private endpoint.",
MarkdownDescription: "Human-readable label that identifies the cloud provider for the Encryption At Rest private endpoint.",
},
"endpoint_id": schema.StringAttribute{
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not required, as an "id" attribute exists in the schema already

Computed: true,
Description: "Unique 24-hexadecimal digit string that identifies the private endpoint.",
MarkdownDescription: "Unique 24-hexadecimal digit string that identifies the private endpoint.",
},
"error_message": schema.StringAttribute{
Computed: true,
Description: "Error message for failures associated with the Encryption At Rest private endpoint.",
Expand Down Expand Up @@ -56,7 +51,6 @@ func ResourceSchema(ctx context.Context) schema.Schema {

type TFEarPrivateEndpointModel struct {
CloudProvider types.String `tfsdk:"cloud_provider"`
EndpointID types.String `tfsdk:"endpoint_id"`
ErrorMessage types.String `tfsdk:"error_message"`
ProjectID types.String `tfsdk:"project_id"`
ID types.String `tfsdk:"id"`
Expand Down