Skip to content

Commit 7f01e61

Browse files
csanxCristina Sánchez Sánchez
andauthored
feat: Add new resource: mongodbatlas_team_project_assignment (#3539)
* WIP - Added resource * Model tests * Resource tests * Migration test * Added error messages * Changelog * Added err * Added a note * Added a note * Added tests to github actions * Fixes * Enhanced test * Updated SDK * Revert "Updated SDK" This reverts commit f8d2d0a. * feat: Add new singular data source: `mongodbatlas_team_project_assignment` (#3544) * Added datasource schema and Read() * Datasource tests * Added changelog * Fix * Comments --------- Co-authored-by: Cristina Sánchez Sánchez <[email protected]> --------- Co-authored-by: Cristina Sánchez Sánchez <[email protected]>
1 parent 278927f commit 7f01e61

File tree

13 files changed

+839
-0
lines changed

13 files changed

+839
-0
lines changed

.changelog/3539.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
resource/mongodbatlas_team_project_assignment
3+
```

.changelog/3544.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-datasource
2+
data-source/mongodbatlas_team_project_assignment
3+
```

.github/workflows/acceptance-tests-runner.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ jobs:
323323
- 'internal/service/projectapikey/*.go'
324324
- 'internal/service/rolesorgid/*.go'
325325
- 'internal/service/team/*.go'
326+
- 'internal/service/teamprojectassignment/*.go'
326327
- 'internal/service/thirdpartyintegration/*.go'
327328
encryption:
328329
- 'internal/service/encryptionatrest/*.go'
@@ -711,6 +712,7 @@ jobs:
711712
./internal/service/apikey
712713
./internal/service/rolesorgid
713714
./internal/service/team
715+
./internal/service/teamprojectassignment
714716
./internal/service/thirdpartyintegration
715717
run: make testacc
716718

internal/provider/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import (
5050
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/streaminstance"
5151
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/streamprivatelinkendpoint"
5252
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/streamprocessor"
53+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/teamprojectassignment"
5354
"github.com/mongodb/terraform-provider-mongodbatlas/version"
5455
)
5556

@@ -463,6 +464,7 @@ func (p *MongodbtlasProvider) DataSources(context.Context) []func() datasource.D
463464
resourcepolicy.PluralDataSource,
464465
clouduserorgassignment.DataSource,
465466
clouduserteamassignment.DataSource,
467+
teamprojectassignment.DataSource,
466468
apikeyprojectassignment.DataSource,
467469
apikeyprojectassignment.PluralDataSource,
468470
}
@@ -491,6 +493,7 @@ func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resou
491493
resourcepolicy.Resource,
492494
clouduserorgassignment.Resource,
493495
apikeyprojectassignment.Resource,
496+
teamprojectassignment.Resource,
494497
clouduserteamassignment.Resource,
495498
}
496499
if config.PreviewProviderV2AdvancedCluster() {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package teamprojectassignment
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/datasource"
7+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/validate"
8+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
9+
)
10+
11+
var _ datasource.DataSource = &teamProjectAssignmentDS{}
12+
var _ datasource.DataSourceWithConfigure = &teamProjectAssignmentDS{}
13+
14+
func DataSource() datasource.DataSource {
15+
return &teamProjectAssignmentDS{
16+
DSCommon: config.DSCommon{
17+
DataSourceName: resourceName,
18+
},
19+
}
20+
}
21+
22+
type teamProjectAssignmentDS struct {
23+
config.DSCommon
24+
}
25+
26+
func (d *teamProjectAssignmentDS) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
27+
resp.Schema = dataSourceSchema()
28+
}
29+
30+
func (d *teamProjectAssignmentDS) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
31+
var state TFModel
32+
resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
33+
if resp.Diagnostics.HasError() {
34+
return
35+
}
36+
37+
connV2 := d.Client.AtlasV2
38+
projectID := state.ProjectId.ValueString()
39+
teamID := state.TeamId.ValueString()
40+
41+
apiResp, httpResp, err := connV2.TeamsApi.GetProjectTeam(ctx, projectID, teamID).Execute()
42+
if err != nil {
43+
if validate.StatusNotFound(httpResp) {
44+
resp.State.RemoveResource(ctx)
45+
return
46+
}
47+
resp.Diagnostics.AddError(errorFetchingResource, err.Error())
48+
return
49+
}
50+
51+
newTeamProjectAssignmentModel, diags := NewTFModel(ctx, apiResp, projectID)
52+
if diags.HasError() {
53+
resp.Diagnostics.Append(diags...)
54+
return
55+
}
56+
resp.Diagnostics.Append(resp.State.Set(ctx, newTeamProjectAssignmentModel)...)
57+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package teamprojectassignment_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
8+
)
9+
10+
func TestMain(m *testing.M) {
11+
cleanup := acc.SetupSharedResources()
12+
exitCode := m.Run()
13+
cleanup()
14+
os.Exit(exitCode)
15+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package teamprojectassignment
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/diag"
7+
"github.com/hashicorp/terraform-plugin-framework/types"
8+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
9+
"go.mongodb.org/atlas-sdk/v20250312006/admin"
10+
)
11+
12+
func NewTFModel(ctx context.Context, apiResp *admin.TeamRole, projectID string) (*TFModel, diag.Diagnostics) {
13+
diags := diag.Diagnostics{}
14+
15+
if apiResp == nil {
16+
return nil, diags
17+
}
18+
19+
roleNames := conversion.TFSetValueOrNull(ctx, apiResp.RoleNames, types.StringType)
20+
21+
return &TFModel{
22+
ProjectId: types.StringValue(projectID),
23+
TeamId: types.StringPointerValue(apiResp.TeamId),
24+
RoleNames: roleNames,
25+
}, diags
26+
}
27+
28+
func buildTeamRole(ctx context.Context, plan *TFModel) *admin.TeamRole {
29+
roleNames := []string{}
30+
if !plan.RoleNames.IsNull() && !plan.RoleNames.IsUnknown() {
31+
roleNames = conversion.TypesSetToString(ctx, plan.RoleNames)
32+
}
33+
34+
return &admin.TeamRole{
35+
TeamId: plan.TeamId.ValueStringPointer(),
36+
RoleNames: &roleNames,
37+
}
38+
}
39+
40+
func NewAtlasReq(ctx context.Context, plan *TFModel) (*[]admin.TeamRole, diag.Diagnostics) {
41+
teamRole := buildTeamRole(ctx, plan)
42+
return &[]admin.TeamRole{*teamRole}, nil
43+
}
44+
45+
func NewAtlasUpdateReq(ctx context.Context, plan *TFModel) (*admin.TeamRole, diag.Diagnostics) {
46+
return buildTeamRole(ctx, plan), nil
47+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package teamprojectassignment_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/types"
7+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/teamprojectassignment"
8+
"github.com/stretchr/testify/assert"
9+
"go.mongodb.org/atlas-sdk/v20250312006/admin"
10+
)
11+
12+
const (
13+
testProjectID = "project-123"
14+
testTeamID = "team-123"
15+
)
16+
17+
var (
18+
testProjectRoles = []string{"PROJECT_OWNER", "PROJECT_READ_ONLY", "PROJECT_MEMBER"}
19+
)
20+
21+
type sdkToTFModelTestCase struct {
22+
SDKResp *admin.TeamRole
23+
expectedTFModel *teamprojectassignment.TFModel
24+
}
25+
26+
func TestTeamProjectAssignmentSDKToTFModel(t *testing.T) {
27+
ctx := t.Context()
28+
29+
fullResp := &admin.TeamRole{
30+
TeamId: admin.PtrString(testTeamID),
31+
RoleNames: &testProjectRoles,
32+
}
33+
34+
expectedRoles, _ := types.SetValueFrom(ctx, types.StringType, testProjectRoles)
35+
expectedFullModel := &teamprojectassignment.TFModel{
36+
ProjectId: types.StringValue(testProjectID),
37+
TeamId: types.StringValue(testTeamID),
38+
RoleNames: expectedRoles,
39+
}
40+
41+
fullNilResp := &admin.TeamRole{
42+
TeamId: admin.PtrString(""),
43+
RoleNames: nil,
44+
}
45+
46+
expectedNilModel := &teamprojectassignment.TFModel{
47+
ProjectId: types.StringValue(testProjectID),
48+
TeamId: types.StringValue(""),
49+
RoleNames: types.SetNull(types.StringType),
50+
}
51+
52+
testCases := map[string]sdkToTFModelTestCase{
53+
"Complete SDK response": {
54+
SDKResp: fullResp,
55+
expectedTFModel: expectedFullModel,
56+
},
57+
"nil SDK response": {
58+
SDKResp: nil,
59+
expectedTFModel: nil,
60+
},
61+
"Empty SDK response": {
62+
SDKResp: fullNilResp,
63+
expectedTFModel: expectedNilModel,
64+
},
65+
}
66+
67+
for testName, tc := range testCases {
68+
t.Run(testName, func(t *testing.T) {
69+
resultModel, diags := teamprojectassignment.NewTFModel(t.Context(), tc.SDKResp, testProjectID)
70+
assert.False(t, diags.HasError(), "expected no diagnostics")
71+
assert.Equal(t, tc.expectedTFModel, resultModel, "TFModel did not match expected")
72+
})
73+
}
74+
}
75+
76+
func TestNewAtlasReq(t *testing.T) {
77+
ctx := t.Context()
78+
79+
roles, _ := types.SetValueFrom(ctx, types.StringType, testProjectRoles)
80+
testCases := map[string]struct {
81+
plan *teamprojectassignment.TFModel
82+
expected *[]admin.TeamRole
83+
}{
84+
"Complete TF state": {
85+
plan: &teamprojectassignment.TFModel{
86+
ProjectId: types.StringValue(testProjectID),
87+
TeamId: types.StringValue(testTeamID),
88+
RoleNames: roles,
89+
},
90+
expected: &[]admin.TeamRole{
91+
{
92+
TeamId: admin.PtrString(testTeamID),
93+
RoleNames: &testProjectRoles,
94+
},
95+
},
96+
},
97+
"No roles": {
98+
plan: &teamprojectassignment.TFModel{
99+
ProjectId: types.StringValue(testProjectID),
100+
TeamId: types.StringValue(testTeamID),
101+
RoleNames: types.SetNull(types.StringType),
102+
},
103+
expected: &[]admin.TeamRole{
104+
{
105+
TeamId: admin.PtrString(testTeamID),
106+
RoleNames: &[]string{},
107+
},
108+
},
109+
},
110+
}
111+
112+
for testName, tc := range testCases {
113+
t.Run(testName, func(t *testing.T) {
114+
apiReqResult, diags := teamprojectassignment.NewAtlasReq(ctx, tc.plan)
115+
assert.False(t, diags.HasError(), "expected no diagnostics")
116+
117+
assert.Len(t, *apiReqResult, len(*tc.expected), "slice lengths don't match")
118+
119+
for i := range *tc.expected {
120+
expectedItem := (*tc.expected)[i]
121+
actualItem := (*apiReqResult)[i]
122+
123+
assert.Equal(t, *expectedItem.TeamId, *actualItem.TeamId, "TeamId values don't match")
124+
125+
if expectedItem.RoleNames == nil {
126+
assert.Nil(t, actualItem.RoleNames, "expected RoleNames to be nil")
127+
} else {
128+
assert.Equal(t, *expectedItem.RoleNames, *actualItem.RoleNames, "RoleNames values don't match")
129+
}
130+
}
131+
})
132+
}
133+
}
134+
135+
func TestNewAtlasUpdateReq(t *testing.T) {
136+
ctx := t.Context()
137+
138+
roles, _ := types.SetValueFrom(ctx, types.StringType, testProjectRoles)
139+
140+
testCases := map[string]struct {
141+
plan *teamprojectassignment.TFModel
142+
expected *admin.TeamRole
143+
}{
144+
"Complete TF state": {
145+
plan: &teamprojectassignment.TFModel{
146+
ProjectId: types.StringValue(testProjectID),
147+
TeamId: types.StringValue(testTeamID),
148+
RoleNames: roles,
149+
},
150+
expected: &admin.TeamRole{
151+
TeamId: admin.PtrString(testTeamID),
152+
RoleNames: &testProjectRoles,
153+
},
154+
},
155+
"No roles": {
156+
plan: &teamprojectassignment.TFModel{
157+
ProjectId: types.StringValue(testProjectID),
158+
TeamId: types.StringValue(testTeamID),
159+
RoleNames: types.SetNull(types.StringType),
160+
},
161+
expected: &admin.TeamRole{
162+
TeamId: admin.PtrString(testTeamID),
163+
RoleNames: &[]string{},
164+
},
165+
},
166+
}
167+
for testName, tc := range testCases {
168+
t.Run(testName, func(t *testing.T) {
169+
apiReqResult, diags := teamprojectassignment.NewAtlasUpdateReq(ctx, tc.plan)
170+
assert.False(t, diags.HasError(), "expected no diagnostics")
171+
172+
assert.Equal(t, *tc.expected.TeamId, *apiReqResult.TeamId, "TeamId values don't match")
173+
174+
if tc.expected.RoleNames == nil {
175+
assert.Nil(t, apiReqResult.RoleNames, "expected RoleNames to be nil")
176+
} else {
177+
assert.Equal(t, *tc.expected.RoleNames, *apiReqResult.RoleNames, "RoleNames values don't match")
178+
}
179+
})
180+
}
181+
}

0 commit comments

Comments
 (0)