Skip to content
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
6 changes: 6 additions & 0 deletions github/data_source_github_organization_custom_properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ func dataSourceGithubOrganizationCustomProperties() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"values_editable_by": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
Expand All @@ -66,6 +71,7 @@ func dataSourceGithubOrganizationCustomPropertiesRead(d *schema.ResourceData, me
_ = d.Set("property_name", propertyAttributes.PropertyName)
_ = d.Set("required", propertyAttributes.Required)
_ = d.Set("value_type", propertyAttributes.ValueType)
_ = d.Set("values_editable_by", propertyAttributes.ValuesEditableBy)

return nil
}
28 changes: 20 additions & 8 deletions github/resource_github_organization_custom_properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ func resourceGithubOrganizationCustomProperties() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"values_editable_by": {
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)",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateDiagFunc: validateValueFunc([]string{"org_actors", "org_and_repo_actors"}),
},
},
}
}
Expand All @@ -78,15 +85,19 @@ func resourceGithubCustomPropertiesCreate(d *schema.ResourceData, meta any) erro
for _, v := range allowedValues {
allowedValuesString = append(allowedValuesString, v.(string))
}
valuesEditableBy := d.Get("values_editable_by").(string)

customProperty := &github.CustomProperty{
PropertyName: &propertyName,
ValueType: valueType,
Required: &required,
DefaultValue: &defaultValue,
Description: &description,
AllowedValues: allowedValuesString,
ValuesEditableBy: &valuesEditableBy,
}

customProperty, _, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, ownerName, d.Get("property_name").(string), &github.CustomProperty{
PropertyName: &propertyName,
ValueType: valueType,
Required: &required,
DefaultValue: &defaultValue,
Description: &description,
AllowedValues: allowedValuesString,
})
customProperty, _, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, ownerName, d.Get("property_name").(string), customProperty)
if err != nil {
return err
}
Expand All @@ -112,6 +123,7 @@ func resourceGithubCustomPropertiesRead(d *schema.ResourceData, meta any) error
_ = d.Set("property_name", customProperty.PropertyName)
_ = d.Set("required", customProperty.Required)
_ = d.Set("value_type", customProperty.ValueType)
_ = d.Set("values_editable_by", customProperty.ValuesEditableBy)

return nil
}
Expand Down
263 changes: 263 additions & 0 deletions github/resource_github_organization_custom_properties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,50 @@ package github

import (
"fmt"
"regexp"
"testing"

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

func TestAccGithubOrganizationCustomPropertiesValidation(t *testing.T) {
t.Run("rejects invalid values_editable_by value", func(t *testing.T) {
config := `
resource "github_organization_custom_properties" "test" {
property_name = "TestInvalidValuesEditableBy"
value_type = "string"
required = false
description = "Test invalid values_editable_by"
values_editable_by = "invalid_value"
}`

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: regexp.MustCompile("invalid_value is an invalid value"),
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})
}

func TestAccGithubOrganizationCustomProperties(t *testing.T) {
t.Run("creates custom property without error", func(t *testing.T) {
config := `
Expand Down Expand Up @@ -151,4 +190,228 @@ func TestAccGithubOrganizationCustomProperties(t *testing.T) {
testCase(t, organization)
})
})

t.Run("creates custom property with values_editable_by without error", func(t *testing.T) {
config := `
resource "github_organization_custom_properties" "test" {
property_name = "TestValuesEditableBy"
value_type = "string"
required = false
description = "Test property for values_editable_by"
values_editable_by = "org_and_repo_actors"
}`

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_organization_custom_properties.test",
"property_name", "TestValuesEditableBy",
),
resource.TestCheckResourceAttr(
"github_organization_custom_properties.test",
"values_editable_by", "org_and_repo_actors",
),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})

t.Run("backward compatibility - property without values_editable_by defaults correctly", func(t *testing.T) {
config := `
resource "github_organization_custom_properties" "test" {
property_name = "TestBackwardCompat"
value_type = "string"
required = false
description = "Test property without values_editable_by"
}`

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_organization_custom_properties.test",
"property_name", "TestBackwardCompat",
),
// When not specified, API returns "org_actors" as the default
resource.TestCheckResourceAttr(
"github_organization_custom_properties.test",
"values_editable_by", "org_actors",
),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})

t.Run("update values_editable_by from org_actors to org_and_repo_actors", func(t *testing.T) {
configBefore := `
resource "github_organization_custom_properties" "test" {
property_name = "TestUpdateValuesEditableBy"
value_type = "string"
required = false
description = "Test updating values_editable_by"
values_editable_by = "org_actors"
}`

configAfter := `
resource "github_organization_custom_properties" "test" {
property_name = "TestUpdateValuesEditableBy"
value_type = "string"
required = false
description = "Test updating values_editable_by"
values_editable_by = "org_and_repo_actors"
}`

const resourceName = "github_organization_custom_properties.test"

checkBefore := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_actors"),
)
checkAfter := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: configBefore,
Check: checkBefore,
},
{
Config: configAfter,
Check: checkAfter,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})

t.Run("imports existing property with values_editable_by set via UI", func(t *testing.T) {
// This test simulates a scenario where values_editable_by was set to
// org_and_repo_actors in the GitHub UI before Terraform support was added.
// The resource config intentionally omits values_editable_by to verify
// Terraform can read and maintain the existing value from the API.

configWithoutField := `
resource "github_organization_custom_properties" "test" {
property_name = "TestImportWithUISet"
value_type = "string"
required = false
description = "Test property set via UI"
}`

// After import, we explicitly set the value in config to match what's in the API
configWithField := `
resource "github_organization_custom_properties" "test" {
property_name = "TestImportWithUISet"
value_type = "string"
required = false
description = "Test property set via UI"
values_editable_by = "org_and_repo_actors"
}`

const resourceName = "github_organization_custom_properties.test"

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
// First, create a property with values_editable_by set
Config: configWithField,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
),
},
{
// Simulate the scenario: config doesn't have values_editable_by
// (as it would have been before Terraform support was added)
// Terraform should read the existing value from the API
Config: configWithoutField,
Check: resource.ComposeTestCheckFunc(
// Terraform should still see the value from the API
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
),
},
{
// Now add it back to the config - should be no changes needed
Config: configWithField,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "values_editable_by", "org_and_repo_actors"),
),
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})
}
4 changes: 3 additions & 1 deletion website/docs/d/organization_custom_properties.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ The following arguments are supported:

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

* `allowed_values` - List of allowed values for the custom property. Only populated when `value_type` is `single_select` or `multi_select`.
* `allowed_values` - List of allowed values for the custom property. Only populated when `value_type` is `single_select` or `multi_select`.

* `values_editable_by` - Who can edit the values of the custom property. Can be one of `org_actors` or `org_and_repo_actors`.
16 changes: 16 additions & 0 deletions website/docs/r/organization_custom_properties.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ resource "github_organization_custom_properties" "environment" {
}
```

## Example Usage - Allow Repository Actors to Edit

This example shows how to allow repository administrators to edit the property values:

```hcl
resource "github_organization_custom_properties" "team_contact" {
property_name = "team_contact"
value_type = "string"
required = false
description = "Contact information for the team managing this repository"
values_editable_by = "org_and_repo_actors"
}
```

## Example Usage - Text Property

```hcl
Expand Down Expand Up @@ -67,6 +81,8 @@ The following arguments are supported:

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

* `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.

## Attributes Reference

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