Skip to content

Commit

Permalink
feat: Adds project tags (#2135)
Browse files Browse the repository at this point in the history
  • Loading branch information
EspenAlbert authored Apr 22, 2024
1 parent 3c45960 commit c765194
Show file tree
Hide file tree
Showing 16 changed files with 432 additions and 6 deletions.
11 changes: 11 additions & 0 deletions .changelog/2135.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/mongodbatlas_project: Adds `tags` attribute
```

```release-note:enhancement
data-source/mongodbatlas_project: Adds `tags` attribute
```

```release-note:enhancement
data-source/mongodbatlas_projects: Adds `tags` attribute
```
3 changes: 2 additions & 1 deletion examples/mongodbatlas_project/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# MongoDB Atlas Provider -- Atlas Project with custom limits
This example creates a Project and defines custom values for certain limits.
This example creates a project with tags and defines custom values for certain limits.
It also shows how you can ignore a specific tag key.

Variables Required to be set:
- `public_key`: Atlas public key
Expand Down
13 changes: 12 additions & 1 deletion examples/mongodbatlas_project/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,15 @@ resource "mongodbatlas_project" "test" {
name = "atlas.project.deployment.nodesPerPrivateLinkRegion"
value = 3
}
}
tags = {
Owner = "Terraform"
Environment = "Example"
Team = "tf-experts"
CurrentDRI = "unset"
}
lifecycle {
ignore_changes = [
tags["CostCenter"]
]
}
}
5 changes: 5 additions & 0 deletions internal/service/project/data_source_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type TFProjectDSModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
ProjectID types.String `tfsdk:"project_id"`
Tags types.Map `tfsdk:"tags"`
Teams []*TFTeamDSModel `tfsdk:"teams"`
Limits []*TFLimitModel `tfsdk:"limits"`
ClusterCount types.Int64 `tfsdk:"cluster_count"`
Expand Down Expand Up @@ -166,6 +167,10 @@ func (d *projectDS) Schema(ctx context.Context, req datasource.SchemaRequest, re
},
},
},
"tags": schema.MapAttribute{
ElementType: types.StringType,
Computed: true,
},
},
}
}
Expand Down
4 changes: 4 additions & 0 deletions internal/service/project/data_source_projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ func (d *ProjectsDS) Schema(ctx context.Context, req datasource.SchemaRequest, r
},
},
},
"tags": schema.MapAttribute{
ElementType: types.StringType,
Computed: true,
},
},
},
},
Expand Down
30 changes: 29 additions & 1 deletion internal/service/project/model_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

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

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

Expand Down Expand Up @@ -34,6 +35,7 @@ func NewTFProjectDataSourceModel(ctx context.Context, project *admin.Group, proj
Teams: NewTFTeamsDataSourceModel(ctx, projectProps.Teams),
Limits: NewTFLimitsDataSourceModel(ctx, projectProps.Limits),
IPAddresses: ipAddressesModel,
Tags: NewTFTags(project.GetTags()),
}, nil
}

Expand Down Expand Up @@ -108,6 +110,7 @@ func NewTFProjectResourceModel(ctx context.Context, projectRes *admin.Group, pro
Teams: newTFTeamsResourceModel(ctx, projectProps.Teams),
Limits: newTFLimitsResourceModel(ctx, projectProps.Limits),
IPAddresses: ipAddressesModel,
Tags: NewTFTags(projectRes.GetTags()),
}

projectSettings := projectProps.Settings
Expand Down Expand Up @@ -167,9 +170,10 @@ func NewTeamRoleList(ctx context.Context, teams []TFTeamModel) *[]admin.TeamRole
return &res
}

func NewGroupUpdate(tfProject *TFProjectRSModel) *admin.GroupUpdate {
func NewGroupUpdate(tfProject *TFProjectRSModel, tags *[]admin.ResourceTag) *admin.GroupUpdate {
return &admin.GroupUpdate{
Name: tfProject.Name.ValueStringPointer(),
Tags: tags,
}
}

Expand Down Expand Up @@ -202,3 +206,27 @@ func UpdateProjectBool(plan, state types.Bool, setting **bool) bool {
}
return false
}

func NewTFTags(tags []admin.ResourceTag) types.Map {
typesTags := make(map[string]attr.Value, len(tags))
for _, tag := range tags {
typesTags[tag.Key] = types.StringValue(tag.Value)
}
return types.MapValueMust(types.StringType, typesTags)
}

func NewResourceTags(ctx context.Context, tags types.Map) []admin.ResourceTag {
if tags.IsNull() || len(tags.Elements()) == 0 {
return []admin.ResourceTag{}
}
elements := make(map[string]types.String, len(tags.Elements()))
_ = tags.ElementsAs(ctx, &elements, false)
var tagsAdmin []admin.ResourceTag
for key, tagValue := range elements {
tagsAdmin = append(tagsAdmin, admin.ResourceTag{
Key: key,
Value: tagValue.ValueString(),
})
}
return tagsAdmin
}
50 changes: 50 additions & 0 deletions internal/service/project/model_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

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

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -234,6 +235,7 @@ func TestProjectDataSourceSDKToDataSourceTFModel(t *testing.T) {
Limits: limitsTF,
IPAddresses: ipAddressesTF,
Created: types.StringValue("0001-01-01T00:00:00Z"),
Tags: emptyTfTags(),
},
},
{
Expand Down Expand Up @@ -266,6 +268,7 @@ func TestProjectDataSourceSDKToDataSourceTFModel(t *testing.T) {
Limits: limitsTF,
IPAddresses: ipAddressesTF,
Created: types.StringValue("0001-01-01T00:00:00Z"),
Tags: emptyTfTags(),
},
},
}
Expand Down Expand Up @@ -316,6 +319,7 @@ func TestProjectDataSourceSDKToResourceTFModel(t *testing.T) {
Limits: limitsTFSet,
IPAddresses: ipAddressesTF,
Created: types.StringValue("0001-01-01T00:00:00Z"),
Tags: emptyTfTags(),
},
},
{
Expand Down Expand Up @@ -347,6 +351,7 @@ func TestProjectDataSourceSDKToResourceTFModel(t *testing.T) {
Limits: limitsTFSet,
IPAddresses: ipAddressesTF,
Created: types.StringValue("0001-01-01T00:00:00Z"),
Tags: emptyTfTags(),
},
},
}
Expand Down Expand Up @@ -540,3 +545,48 @@ func TestUpdateProjectBool(t *testing.T) {
})
}
}

func TestNewResourceTags(t *testing.T) {
testCases := map[string]struct {
plan types.Map
expected []admin.ResourceTag
}{
"tags null": {types.MapNull(types.StringType), []admin.ResourceTag{}},
"tags unknown": {types.MapUnknown(types.StringType), []admin.ResourceTag{}},
"tags convert normally": {types.MapValueMust(types.StringType, map[string]attr.Value{
"key1": types.StringValue("value1"),
}), []admin.ResourceTag{
*admin.NewResourceTag("key1", "value1"),
}},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert.Equal(t, tc.expected, project.NewResourceTags(context.Background(), tc.plan))
})
}
}

func TestNewTFTags(t *testing.T) {
var (
tfMapEmpty = emptyTfTags()
apiListEmpty = []admin.ResourceTag{}
apiSingleTag = []admin.ResourceTag{*admin.NewResourceTag("key1", "value1")}
tfMapSingleTag = types.MapValueMust(types.StringType, map[string]attr.Value{"key1": types.StringValue("value1")})
)
testCases := map[string]struct {
expected types.Map
adminTags []admin.ResourceTag
}{
"api empty list tf null should give map null": {tfMapEmpty, apiListEmpty},
"tags single value tf null should give map single": {tfMapSingleTag, apiSingleTag},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert.Equal(t, tc.expected, project.NewTFTags(tc.adminTags))
})
}
}

func emptyTfTags() types.Map {
return types.MapValueMust(types.StringType, map[string]attr.Value{})
}
17 changes: 14 additions & 3 deletions internal/service/project/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type projectRS struct {
type TFProjectRSModel struct {
Limits types.Set `tfsdk:"limits"`
Teams types.Set `tfsdk:"teams"`
Tags types.Map `tfsdk:"tags"`
IPAddresses types.Object `tfsdk:"ip_addresses"`
RegionUsageRestrictions types.String `tfsdk:"region_usage_restrictions"`
Name types.String `tfsdk:"name"`
Expand Down Expand Up @@ -245,6 +246,10 @@ func (r *projectRS) Schema(ctx context.Context, req resource.SchemaRequest, resp
},
},
},
"tags": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
},
},
Blocks: map[string]schema.Block{
"teams": schema.SetNestedBlock{
Expand Down Expand Up @@ -301,12 +306,13 @@ func (r *projectRS) Create(ctx context.Context, req resource.CreateRequest, resp
if resp.Diagnostics.HasError() {
return
}

tags := NewResourceTags(ctx, projectPlan.Tags)
projectGroup := &admin.Group{
OrgId: projectPlan.OrgID.ValueString(),
Name: projectPlan.Name.ValueString(),
WithDefaultAlertsSettings: projectPlan.WithDefaultAlertsSettings.ValueBoolPointer(),
RegionUsageRestrictions: projectPlan.RegionUsageRestrictions.ValueStringPointer(),
Tags: &tags,
}

projectAPIParams := &admin.CreateProjectApiParams{
Expand Down Expand Up @@ -576,6 +582,9 @@ func updatePlanFromConfig(projectPlanNewPtr, projectPlan *TFProjectRSModel) {
// https://discuss.hashicorp.com/t/boolean-optional-default-value-migration-to-framework/55932
projectPlanNewPtr.WithDefaultAlertsSettings = projectPlan.WithDefaultAlertsSettings
projectPlanNewPtr.ProjectOwnerID = projectPlan.ProjectOwnerID
if projectPlan.Tags.IsNull() && len(projectPlanNewPtr.Tags.Elements()) == 0 {
projectPlanNewPtr.Tags = types.MapNull(types.StringType)
}
}

func FilterUserDefinedLimits(allAtlasLimits []admin.DataFederationLimit, tflimits []TFLimitModel) []admin.DataFederationLimit {
Expand Down Expand Up @@ -776,13 +785,15 @@ func hasLimitsChanged(planLimits, stateLimits []TFLimitModel) bool {
}

func UpdateProject(ctx context.Context, projectsAPI admin.ProjectsApi, projectState, projectPlan *TFProjectRSModel) error {
if projectPlan.Name.Equal(projectState.Name) {
tagsBefore := NewResourceTags(ctx, projectState.Tags)
tagsAfter := NewResourceTags(ctx, projectPlan.Tags)
if projectPlan.Name.Equal(projectState.Name) && reflect.DeepEqual(tagsBefore, tagsAfter) {
return nil
}

projectID := projectState.ID.ValueString()

if _, _, err := projectsAPI.UpdateProject(ctx, projectID, NewGroupUpdate(projectPlan)).Execute(); err != nil {
if _, _, err := projectsAPI.UpdateProject(ctx, projectID, NewGroupUpdate(projectPlan, &tagsAfter)).Execute(); err != nil {
return fmt.Errorf("error updating the project(%s): %s", projectID, err)
}

Expand Down
Loading

0 comments on commit c765194

Please sign in to comment.