Skip to content

Commit 895ae19

Browse files
markszabodiofeher
andauthored
feat: Add values_editable_by support to github_organization_custom_properties (#2931)
* Add ValuesEditableBy support to github_organization_custom_properties * Validate values * Add more tests * Use value parsing the same way the others are parsed --------- Co-authored-by: Diógenes Fernandes <diofeher@gmail.com>
1 parent d866b8e commit 895ae19

5 files changed

+308
-9
lines changed

github/data_source_github_organization_custom_properties.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ func dataSourceGithubOrganizationCustomProperties() *schema.Resource {
4040
Computed: true,
4141
Elem: &schema.Schema{Type: schema.TypeString},
4242
},
43+
"values_editable_by": {
44+
Type: schema.TypeString,
45+
Optional: true,
46+
Computed: true,
47+
},
4348
},
4449
}
4550
}
@@ -66,6 +71,7 @@ func dataSourceGithubOrganizationCustomPropertiesRead(d *schema.ResourceData, me
6671
_ = d.Set("property_name", propertyAttributes.PropertyName)
6772
_ = d.Set("required", propertyAttributes.Required)
6873
_ = d.Set("value_type", propertyAttributes.ValueType)
74+
_ = d.Set("values_editable_by", propertyAttributes.ValuesEditableBy)
6975

7076
return nil
7177
}

github/resource_github_organization_custom_properties.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ func resourceGithubOrganizationCustomProperties() *schema.Resource {
5959
Computed: true,
6060
Elem: &schema.Schema{Type: schema.TypeString},
6161
},
62+
"values_editable_by": {
63+
Description: "Who can edit the values of the custom property. Can be one of 'org_actors' or 'org_and_repo_actors'. If not specified, the default is 'org_actors' (only organization owners can edit values)",
64+
Type: schema.TypeString,
65+
Optional: true,
66+
Computed: true,
67+
ValidateDiagFunc: validateValueFunc([]string{"org_actors", "org_and_repo_actors"}),
68+
},
6269
},
6370
}
6471
}
@@ -78,15 +85,19 @@ func resourceGithubCustomPropertiesCreate(d *schema.ResourceData, meta any) erro
7885
for _, v := range allowedValues {
7986
allowedValuesString = append(allowedValuesString, v.(string))
8087
}
88+
valuesEditableBy := d.Get("values_editable_by").(string)
89+
90+
customProperty := &github.CustomProperty{
91+
PropertyName: &propertyName,
92+
ValueType: valueType,
93+
Required: &required,
94+
DefaultValue: &defaultValue,
95+
Description: &description,
96+
AllowedValues: allowedValuesString,
97+
ValuesEditableBy: &valuesEditableBy,
98+
}
8199

82-
customProperty, _, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, ownerName, d.Get("property_name").(string), &github.CustomProperty{
83-
PropertyName: &propertyName,
84-
ValueType: valueType,
85-
Required: &required,
86-
DefaultValue: &defaultValue,
87-
Description: &description,
88-
AllowedValues: allowedValuesString,
89-
})
100+
customProperty, _, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, ownerName, d.Get("property_name").(string), customProperty)
90101
if err != nil {
91102
return err
92103
}
@@ -112,6 +123,7 @@ func resourceGithubCustomPropertiesRead(d *schema.ResourceData, meta any) error
112123
_ = d.Set("property_name", customProperty.PropertyName)
113124
_ = d.Set("required", customProperty.Required)
114125
_ = d.Set("value_type", customProperty.ValueType)
126+
_ = d.Set("values_editable_by", customProperty.ValuesEditableBy)
115127

116128
return nil
117129
}

github/resource_github_organization_custom_properties_test.go

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,50 @@ package github
22

33
import (
44
"fmt"
5+
"regexp"
56
"testing"
67

78
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
89
)
910

11+
func TestAccGithubOrganizationCustomPropertiesValidation(t *testing.T) {
12+
t.Run("rejects invalid values_editable_by value", func(t *testing.T) {
13+
config := `
14+
resource "github_organization_custom_properties" "test" {
15+
property_name = "TestInvalidValuesEditableBy"
16+
value_type = "string"
17+
required = false
18+
description = "Test invalid values_editable_by"
19+
values_editable_by = "invalid_value"
20+
}`
21+
22+
testCase := func(t *testing.T, mode string) {
23+
resource.Test(t, resource.TestCase{
24+
PreCheck: func() { skipUnlessMode(t, mode) },
25+
Providers: testAccProviders,
26+
Steps: []resource.TestStep{
27+
{
28+
Config: config,
29+
ExpectError: regexp.MustCompile("invalid_value is an invalid value"),
30+
},
31+
},
32+
})
33+
}
34+
35+
t.Run("with an anonymous account", func(t *testing.T) {
36+
t.Skip("anonymous account not supported for this operation")
37+
})
38+
39+
t.Run("with an individual account", func(t *testing.T) {
40+
t.Skip("individual account not supported for this operation")
41+
})
42+
43+
t.Run("with an organization account", func(t *testing.T) {
44+
testCase(t, organization)
45+
})
46+
})
47+
}
48+
1049
func TestAccGithubOrganizationCustomProperties(t *testing.T) {
1150
t.Run("creates custom property without error", func(t *testing.T) {
1251
config := `
@@ -151,4 +190,228 @@ func TestAccGithubOrganizationCustomProperties(t *testing.T) {
151190
testCase(t, organization)
152191
})
153192
})
193+
194+
t.Run("creates custom property with values_editable_by without error", func(t *testing.T) {
195+
config := `
196+
resource "github_organization_custom_properties" "test" {
197+
property_name = "TestValuesEditableBy"
198+
value_type = "string"
199+
required = false
200+
description = "Test property for values_editable_by"
201+
values_editable_by = "org_and_repo_actors"
202+
}`
203+
204+
check := resource.ComposeTestCheckFunc(
205+
resource.TestCheckResourceAttr(
206+
"github_organization_custom_properties.test",
207+
"property_name", "TestValuesEditableBy",
208+
),
209+
resource.TestCheckResourceAttr(
210+
"github_organization_custom_properties.test",
211+
"values_editable_by", "org_and_repo_actors",
212+
),
213+
)
214+
215+
testCase := func(t *testing.T, mode string) {
216+
resource.Test(t, resource.TestCase{
217+
PreCheck: func() { skipUnlessMode(t, mode) },
218+
Providers: testAccProviders,
219+
Steps: []resource.TestStep{
220+
{
221+
Config: config,
222+
Check: check,
223+
},
224+
},
225+
})
226+
}
227+
228+
t.Run("with an anonymous account", func(t *testing.T) {
229+
t.Skip("anonymous account not supported for this operation")
230+
})
231+
232+
t.Run("with an individual account", func(t *testing.T) {
233+
t.Skip("individual account not supported for this operation")
234+
})
235+
236+
t.Run("with an organization account", func(t *testing.T) {
237+
testCase(t, organization)
238+
})
239+
})
240+
241+
t.Run("backward compatibility - property without values_editable_by defaults correctly", func(t *testing.T) {
242+
config := `
243+
resource "github_organization_custom_properties" "test" {
244+
property_name = "TestBackwardCompat"
245+
value_type = "string"
246+
required = false
247+
description = "Test property without values_editable_by"
248+
}`
249+
250+
check := resource.ComposeTestCheckFunc(
251+
resource.TestCheckResourceAttr(
252+
"github_organization_custom_properties.test",
253+
"property_name", "TestBackwardCompat",
254+
),
255+
// When not specified, API returns "org_actors" as the default
256+
resource.TestCheckResourceAttr(
257+
"github_organization_custom_properties.test",
258+
"values_editable_by", "org_actors",
259+
),
260+
)
261+
262+
testCase := func(t *testing.T, mode string) {
263+
resource.Test(t, resource.TestCase{
264+
PreCheck: func() { skipUnlessMode(t, mode) },
265+
Providers: testAccProviders,
266+
Steps: []resource.TestStep{
267+
{
268+
Config: config,
269+
Check: check,
270+
},
271+
},
272+
})
273+
}
274+
275+
t.Run("with an anonymous account", func(t *testing.T) {
276+
t.Skip("anonymous account not supported for this operation")
277+
})
278+
279+
t.Run("with an individual account", func(t *testing.T) {
280+
t.Skip("individual account not supported for this operation")
281+
})
282+
283+
t.Run("with an organization account", func(t *testing.T) {
284+
testCase(t, organization)
285+
})
286+
})
287+
288+
t.Run("update values_editable_by from org_actors to org_and_repo_actors", func(t *testing.T) {
289+
configBefore := `
290+
resource "github_organization_custom_properties" "test" {
291+
property_name = "TestUpdateValuesEditableBy"
292+
value_type = "string"
293+
required = false
294+
description = "Test updating values_editable_by"
295+
values_editable_by = "org_actors"
296+
}`
297+
298+
configAfter := `
299+
resource "github_organization_custom_properties" "test" {
300+
property_name = "TestUpdateValuesEditableBy"
301+
value_type = "string"
302+
required = false
303+
description = "Test updating values_editable_by"
304+
values_editable_by = "org_and_repo_actors"
305+
}`
306+
307+
const resourceName = "github_organization_custom_properties.test"
308+
309+
checkBefore := resource.ComposeTestCheckFunc(
310+
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_actors"),
311+
)
312+
checkAfter := resource.ComposeTestCheckFunc(
313+
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
314+
)
315+
316+
testCase := func(t *testing.T, mode string) {
317+
resource.Test(t, resource.TestCase{
318+
PreCheck: func() { skipUnlessMode(t, mode) },
319+
Providers: testAccProviders,
320+
Steps: []resource.TestStep{
321+
{
322+
Config: configBefore,
323+
Check: checkBefore,
324+
},
325+
{
326+
Config: configAfter,
327+
Check: checkAfter,
328+
},
329+
},
330+
})
331+
}
332+
333+
t.Run("with an anonymous account", func(t *testing.T) {
334+
t.Skip("anonymous account not supported for this operation")
335+
})
336+
337+
t.Run("with an individual account", func(t *testing.T) {
338+
t.Skip("individual account not supported for this operation")
339+
})
340+
341+
t.Run("with an organization account", func(t *testing.T) {
342+
testCase(t, organization)
343+
})
344+
})
345+
346+
t.Run("imports existing property with values_editable_by set via UI", func(t *testing.T) {
347+
// This test simulates a scenario where values_editable_by was set to
348+
// org_and_repo_actors in the GitHub UI before Terraform support was added.
349+
// The resource config intentionally omits values_editable_by to verify
350+
// Terraform can read and maintain the existing value from the API.
351+
352+
configWithoutField := `
353+
resource "github_organization_custom_properties" "test" {
354+
property_name = "TestImportWithUISet"
355+
value_type = "string"
356+
required = false
357+
description = "Test property set via UI"
358+
}`
359+
360+
// After import, we explicitly set the value in config to match what's in the API
361+
configWithField := `
362+
resource "github_organization_custom_properties" "test" {
363+
property_name = "TestImportWithUISet"
364+
value_type = "string"
365+
required = false
366+
description = "Test property set via UI"
367+
values_editable_by = "org_and_repo_actors"
368+
}`
369+
370+
const resourceName = "github_organization_custom_properties.test"
371+
372+
testCase := func(t *testing.T, mode string) {
373+
resource.Test(t, resource.TestCase{
374+
PreCheck: func() { skipUnlessMode(t, mode) },
375+
Providers: testAccProviders,
376+
Steps: []resource.TestStep{
377+
{
378+
// First, create a property with values_editable_by set
379+
Config: configWithField,
380+
Check: resource.ComposeTestCheckFunc(
381+
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
382+
),
383+
},
384+
{
385+
// Simulate the scenario: config doesn't have values_editable_by
386+
// (as it would have been before Terraform support was added)
387+
// Terraform should read the existing value from the API
388+
Config: configWithoutField,
389+
Check: resource.ComposeTestCheckFunc(
390+
// Terraform should still see the value from the API
391+
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
392+
),
393+
},
394+
{
395+
// Now add it back to the config - should be no changes needed
396+
Config: configWithField,
397+
Check: resource.ComposeTestCheckFunc(
398+
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
399+
),
400+
},
401+
},
402+
})
403+
}
404+
405+
t.Run("with an anonymous account", func(t *testing.T) {
406+
t.Skip("anonymous account not supported for this operation")
407+
})
408+
409+
t.Run("with an individual account", func(t *testing.T) {
410+
t.Skip("individual account not supported for this operation")
411+
})
412+
413+
t.Run("with an organization account", func(t *testing.T) {
414+
testCase(t, organization)
415+
})
416+
})
154417
}

website/docs/d/organization_custom_properties.html.markdown

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ The following arguments are supported:
3535

3636
* `default_value` - The default value of the custom property.
3737

38-
* `allowed_values` - List of allowed values for the custom property. Only populated when `value_type` is `single_select` or `multi_select`.
38+
* `allowed_values` - List of allowed values for the custom property. Only populated when `value_type` is `single_select` or `multi_select`.
39+
40+
* `values_editable_by` - Who can edit the values of the custom property. Can be one of `org_actors` or `org_and_repo_actors`.

website/docs/r/organization_custom_properties.html.markdown

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@ resource "github_organization_custom_properties" "environment" {
2828
}
2929
```
3030

31+
## Example Usage - Allow Repository Actors to Edit
32+
33+
This example shows how to allow repository administrators to edit the property values:
34+
35+
```hcl
36+
resource "github_organization_custom_properties" "team_contact" {
37+
property_name = "team_contact"
38+
value_type = "string"
39+
required = false
40+
description = "Contact information for the team managing this repository"
41+
values_editable_by = "org_and_repo_actors"
42+
}
43+
```
44+
3145
## Example Usage - Text Property
3246

3347
```hcl
@@ -67,6 +81,8 @@ The following arguments are supported:
6781

6882
* `allowed_values` - (Optional) List of allowed values for the custom property. Only applicable when `value_type` is `single_select` or `multi_select`.
6983

84+
* `values_editable_by` - (Optional) Who can edit the values of the custom property. Can be one of `org_actors` or `org_and_repo_actors`. When set to `org_actors` (the default), only organization owners can edit the property values on repositories. When set to `org_and_repo_actors`, both organization owners and repository administrators with the custom properties permission can edit the values.
85+
7086
## Attributes Reference
7187

7288
In addition to all arguments above, the following attributes are exported:

0 commit comments

Comments
 (0)