Skip to content

Commit 08d0331

Browse files
authored
feat: project mode (#215)
1 parent 2434aed commit 08d0331

13 files changed

+160
-17
lines changed

docs/data-sources/project.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ data "unleash_project" "test" {
2828
### Optional
2929

3030
- `description` (String) A description of the project's purpose.
31+
- `mode` (String) The project's collaboration mode. Determines whether non project members can submit change requests and the projects visibility to non members. Valid values are 'open', 'protected' and 'private'. If a value is not set, the project will default to 'open'
3132

3233
### Read-Only
3334

docs/resources/project.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ resource "unleash_project" "test_project" {
4242
### Optional
4343

4444
- `description` (String) A description of the project's purpose.
45+
- `mode` (String) The project's collaboration mode. Determines whether non project members can submit change requests and the projects visibility to non members. Valid values are 'open', 'protected' and 'private'. If a value is not set, the project will default to 'open'

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.18
44

55
require (
66
github.com/Masterminds/semver v1.5.0
7-
github.com/Unleash/unleash-server-api-go v0.5.0
7+
github.com/Unleash/unleash-server-api-go v0.5.1
88
github.com/fatih/structs v1.1.0
99
github.com/hashicorp/terraform-plugin-docs v0.18.0
1010
github.com/hashicorp/terraform-plugin-framework v1.4.2

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
1313
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
1414
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
1515
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
16-
github.com/Unleash/unleash-server-api-go v0.5.0 h1:pXV743Y5ZbGjvGWhL78THu3S4FrwjxvxU8Fwg0SI1mM=
17-
github.com/Unleash/unleash-server-api-go v0.5.0/go.mod h1:/IvCtBfBrOVOa67elCLig45kt6NYkk79oxmRW80WOFY=
16+
github.com/Unleash/unleash-server-api-go v0.5.1 h1:RFprAg66kfj/A7kwpiYeOqO1lpgAF8rHEODAsnYMC4U=
17+
github.com/Unleash/unleash-server-api-go v0.5.1/go.mod h1:/IvCtBfBrOVOa67elCLig45kt6NYkk79oxmRW80WOFY=
1818
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
1919
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
2020
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=

internal/provider/project_data_source.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type projectDataSourceModel struct {
2828
Id types.String `tfsdk:"id"`
2929
Name types.String `tfsdk:"name"`
3030
Description types.String `tfsdk:"description"`
31+
Mode types.String `tfsdk:"mode"`
3132
}
3233

3334
func (d *projectDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) {
@@ -64,6 +65,13 @@ func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
6465
Description: "A description of the project's purpose.",
6566
Optional: true,
6667
},
68+
"mode": schema.StringAttribute{
69+
Description: "The project's collaboration mode. Determines whether non project members can submit " +
70+
"change requests and the projects visibility to non members. Valid values are 'open', 'protected' and 'private'." +
71+
" If a value is not set, the project will default to 'open'",
72+
Optional: true,
73+
Computed: true,
74+
},
6775
},
6876
}
6977
}
@@ -98,6 +106,14 @@ func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest
98106
state.Description = types.StringNull()
99107
}
100108

109+
if project.Mode != nil {
110+
state.Mode = types.StringValue(*project.Mode)
111+
} else {
112+
// From checking the API spec I don't believe this actually can happen but this gives us a nice
113+
// chance to have some backwards compatibility with older versions of the API where open was the only mode
114+
state.Mode = types.StringValue("open")
115+
}
116+
101117
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
102118
tflog.Debug(ctx, "Finished reading user data source", map[string]any{"success": true})
103119
}

internal/provider/project_data_source_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func TestAccProjectDataSource(t *testing.T) {
2121
resource.TestCheckResourceAttr("data.unleash_project.test", "name", "Default"),
2222
resource.TestCheckResourceAttr("data.unleash_project.test", "id", "default"),
2323
resource.TestCheckResourceAttr("data.unleash_project.test", "description", "Default project"),
24+
resource.TestCheckResourceAttr("data.unleash_project.test", "mode", "open"),
2425
),
2526
},
2627
},

internal/provider/project_resource.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type projectResourceModel struct {
3030
Id types.String `tfsdk:"id"`
3131
Name types.String `tfsdk:"name"`
3232
Description types.String `tfsdk:"description"`
33+
Mode types.String `tfsdk:"mode"`
3334
}
3435

3536
// Configure adds the provider configured client to the data source.
@@ -69,6 +70,13 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re
6970
Description: "A description of the project's purpose.",
7071
Optional: true,
7172
},
73+
"mode": schema.StringAttribute{
74+
Description: "The project's collaboration mode. Determines whether non project members can submit " +
75+
"change requests and the projects visibility to non members. Valid values are 'open', 'protected' and 'private'." +
76+
" If a value is not set, the project will default to 'open'",
77+
Computed: true,
78+
Optional: true,
79+
},
7280
},
7381
}
7482
}
@@ -104,8 +112,25 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
104112
return
105113
}
106114

115+
mode, err := resolveRequestedMode(plan)
116+
if err != nil {
117+
resp.Diagnostics.AddError(err.Error(), "InvalidMode")
118+
return
119+
}
120+
121+
updateProjectSettingsRequest := *unleash.NewUpdateProjectEnterpriseSettingsSchemaWithDefaults()
122+
updateProjectSettingsRequest.SetMode(mode)
123+
124+
updateSettingsResponse, err := r.client.ProjectsAPI.UpdateProjectEnterpriseSettings(context.Background(), *plan.Id.ValueStringPointer()).UpdateProjectEnterpriseSettingsSchema(updateProjectSettingsRequest).Execute()
125+
126+
if !ValidateApiResponse(updateSettingsResponse, 200, &resp.Diagnostics, err) {
127+
return
128+
}
129+
107130
plan.Id = types.StringValue(project.Id)
108131
plan.Name = types.StringValue(project.Name)
132+
plan.Mode = types.StringValue(mode)
133+
109134
if project.Description.IsSet() {
110135
plan.Description = types.StringValue(*project.Description.Get())
111136
} else {
@@ -147,6 +172,8 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
147172
state.Id = types.StringValue(fmt.Sprintf("%v", project.Id))
148173
state.Name = types.StringValue(fmt.Sprintf("%v", project.Name))
149174

175+
setModelMode(project.Mode, &state)
176+
150177
if project.Description.IsSet() && project.Description.Get() != nil {
151178
state.Description = types.StringValue(*project.Description.Get())
152179
} else {
@@ -172,6 +199,21 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
172199
updateProjectSchema.Description = state.Description.ValueStringPointer()
173200
}
174201

202+
mode, err := resolveRequestedMode(state)
203+
if err != nil {
204+
resp.Diagnostics.AddError(err.Error(), "InvalidMode")
205+
return
206+
}
207+
208+
updateProjectSettingsRequest := *unleash.NewUpdateProjectEnterpriseSettingsSchemaWithDefaults()
209+
updateProjectSettingsRequest.SetMode(mode)
210+
211+
updateSettingsResponse, err := r.client.ProjectsAPI.UpdateProjectEnterpriseSettings(context.Background(), *state.Id.ValueStringPointer()).UpdateProjectEnterpriseSettingsSchema(updateProjectSettingsRequest).Execute()
212+
213+
if !ValidateApiResponse(updateSettingsResponse, 200, &resp.Diagnostics, err) {
214+
return
215+
}
216+
175217
req.State.Get(ctx, &state)
176218

177219
api_response, err := r.client.ProjectsAPI.UpdateProject(ctx, state.Id.ValueString()).UpdateProjectSchema(updateProjectSchema).Execute()
@@ -196,6 +238,8 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
196238
state.Id = types.StringValue(fmt.Sprintf("%v", project.Id))
197239
state.Name = types.StringValue(fmt.Sprintf("%v", project.Name))
198240

241+
setModelMode(project.Mode, &state)
242+
199243
if project.Description.IsSet() {
200244
state.Description = types.StringValue(*project.Description.Get())
201245
} else {
@@ -225,3 +269,25 @@ func (r *projectResource) Delete(ctx context.Context, req resource.DeleteRequest
225269
resp.State.RemoveResource(ctx)
226270
tflog.Debug(ctx, "Deleted item resource", map[string]any{"success": true})
227271
}
272+
273+
func setModelMode(mode *string, model *projectResourceModel) {
274+
if mode != nil {
275+
model.Mode = types.StringValue(*mode)
276+
} else {
277+
// From checking the API spec I don't believe this actually can happen but this gives us a nice
278+
// chance to have some backwards compatibility with older versions of the API where open was the only mode
279+
model.Mode = types.StringValue("open")
280+
}
281+
}
282+
283+
func resolveRequestedMode(plan projectResourceModel) (string, error) {
284+
if !plan.Mode.IsNull() && plan.Mode.ValueString() != "" && plan.Mode.ValueString() != "open" && plan.Mode.ValueString() != "protected" && plan.Mode.ValueString() != "private" {
285+
return "", fmt.Errorf("project mode must be unset or set to 'open', 'protected' or 'private'. Got: '%s'", plan.Mode.ValueString())
286+
}
287+
288+
if !plan.Mode.IsNull() && plan.Mode.ValueString() != "" {
289+
return plan.Mode.ValueString(), nil
290+
} else {
291+
return "open", nil
292+
}
293+
}

internal/provider/project_resource_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ func testAccSampleProjectResourceWithNoDescription(name string, id string) strin
2525
}`, id, name)
2626
}
2727

28+
func testAccSampleProjectResourceWithMode(id string, mode string) string {
29+
return fmt.Sprintf(`
30+
resource "unleash_project" "test_project3" {
31+
id = "%s"
32+
name = "TestProjectName"
33+
description = "test description"
34+
mode = "%s"
35+
}`, id, mode)
36+
}
37+
2838
func TestAccProjectResource(t *testing.T) {
2939
if os.Getenv("UNLEASH_ENTERPRISE") != "true" {
3040
t.Skip("Skipping enterprise tests")
@@ -65,6 +75,22 @@ func TestAccProjectResource(t *testing.T) {
6575
ImportState: true,
6676
ImportStateVerify: true,
6777
},
78+
{
79+
Config: testAccSampleProjectResourceWithMode("TestId3", "open"),
80+
Check: resource.ComposeAggregateTestCheckFunc(
81+
resource.TestCheckResourceAttrSet("unleash_project.test_project3", "id"),
82+
resource.TestCheckResourceAttrSet("unleash_project.test_project3", "name"),
83+
resource.TestCheckResourceAttr("unleash_project.test_project3", "mode", "open"),
84+
),
85+
},
86+
{
87+
Config: testAccSampleProjectResourceWithMode("TestId3", "private"),
88+
Check: resource.ComposeAggregateTestCheckFunc(
89+
resource.TestCheckResourceAttrSet("unleash_project.test_project3", "id"),
90+
resource.TestCheckResourceAttrSet("unleash_project.test_project3", "name"),
91+
resource.TestCheckResourceAttr("unleash_project.test_project3", "mode", "private"),
92+
),
93+
},
6894
},
6995
})
7096
}

internal/provider/role_resource.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func (r *roleResource) Create(ctx context.Context, req resource.CreateRequest, r
132132
Name: plannedPermission.Name.ValueString(),
133133
}
134134
if !plannedPermission.Environment.IsNull() && !plannedPermission.Environment.IsUnknown() {
135-
permissionRef.Environment = plannedPermission.Environment.ValueStringPointer()
135+
permissionRef.SetEnvironment(*plannedPermission.Environment.ValueStringPointer())
136136
}
137137
permissions = append(permissions, permissionRef)
138138
}
@@ -167,8 +167,8 @@ func (r *roleResource) Create(ctx context.Context, req resource.CreateRequest, r
167167
permissionRef := permissionRef{
168168
Name: types.StringValue(requestPermission.Name),
169169
}
170-
if requestPermission.Environment != nil {
171-
permissionRef.Environment = types.StringValue(*requestPermission.Environment)
170+
if requestPermission.Environment.IsSet() {
171+
permissionRef.Environment = types.StringValue(*requestPermission.Environment.Get())
172172
}
173173
newPermissions = append(newPermissions, permissionRef)
174174
}
@@ -251,7 +251,7 @@ func (r *roleResource) Update(ctx context.Context, req resource.UpdateRequest, r
251251
Name: plannedPermission.Name.ValueString(),
252252
}
253253
if !plannedPermission.Environment.IsNull() && !plannedPermission.Environment.IsUnknown() {
254-
permissionRef.Environment = plannedPermission.Environment.ValueStringPointer()
254+
permissionRef.SetEnvironment(plannedPermission.Environment.ValueString())
255255
}
256256
permissions = append(permissions, permissionRef)
257257
}
@@ -286,8 +286,8 @@ func (r *roleResource) Update(ctx context.Context, req resource.UpdateRequest, r
286286
permissionRef := permissionRef{
287287
Name: types.StringValue(foundPermissions.Name),
288288
}
289-
if foundPermissions.Environment != nil {
290-
permissionRef.Environment = types.StringValue(*foundPermissions.Environment)
289+
if foundPermissions.Environment.IsSet() {
290+
permissionRef.Environment = types.StringValue(*foundPermissions.Environment.Get())
291291
}
292292
freshPermissions = append(freshPermissions, permissionRef)
293293
}

internal/provider/service_account_resource.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ func (r *serviceAccountResource) Create(ctx context.Context, req resource.Create
104104
roleId32 := int32(roleId.ValueInt64())
105105

106106
createSchema := unleash.CreateServiceAccountSchema{
107-
Username: plan.UserName.ValueStringPointer(),
108-
Name: plan.Name.ValueStringPointer(),
107+
Username: *plan.UserName.ValueStringPointer(),
108+
Name: *plan.Name.ValueStringPointer(),
109109
RootRole: roleId32,
110110
}
111111

0 commit comments

Comments
 (0)