From 16ff64c43388d5ca966d02e82e247cabe3bc7722 Mon Sep 17 00:00:00 2001 From: milan-dewilde_liantis Date: Tue, 28 Apr 2026 10:00:31 +0200 Subject: [PATCH] fix: support in-place updates for repository custom property values The github_repository_custom_property resource declared property_value as ForceNew with no Update function, causing every value change to plan as a destroy+create. The destroy step calls the upsert API with a nil value, which unsets the property on GitHub before the create step rewrites it. Wire an Update function that calls the same upsert as Create, and remove ForceNew from property_value. Identity fields (repository, property_name, property_type) remain ForceNew. --- ...ource_github_repository_custom_property.go | 9 ++- ..._github_repository_custom_property_test.go | 74 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/github/resource_github_repository_custom_property.go b/github/resource_github_repository_custom_property.go index 9da207a1be..8211003ed0 100644 --- a/github/resource_github_repository_custom_property.go +++ b/github/resource_github_repository_custom_property.go @@ -13,6 +13,7 @@ func resourceGithubRepositoryCustomProperty() *schema.Resource { return &schema.Resource{ Create: resourceGithubRepositoryCustomPropertyCreate, Read: resourceGithubRepositoryCustomPropertyRead, + Update: resourceGithubRepositoryCustomPropertyUpdate, Delete: resourceGithubRepositoryCustomPropertyDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -46,7 +47,6 @@ func resourceGithubRepositoryCustomProperty() *schema.Resource { Elem: &schema.Schema{ Type: schema.TypeString, }, - ForceNew: true, }, }, } @@ -86,6 +86,13 @@ func resourceGithubRepositoryCustomPropertyCreate(d *schema.ResourceData, meta a return resourceGithubRepositoryCustomPropertyRead(d, meta) } +func resourceGithubRepositoryCustomPropertyUpdate(d *schema.ResourceData, meta any) error { + if err := resourceGithubRepositoryCustomPropertyCreate(d, meta); err != nil { + return err + } + return resourceGithubRepositoryCustomPropertyRead(d, meta) +} + func resourceGithubRepositoryCustomPropertyRead(d *schema.ResourceData, meta any) error { client := meta.(*Owner).v3client ctx := context.Background() diff --git a/github/resource_github_repository_custom_property_test.go b/github/resource_github_repository_custom_property_test.go index 9fcb28b496..92eefb9b88 100644 --- a/github/resource_github_repository_custom_property_test.go +++ b/github/resource_github_repository_custom_property_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubRepositoryCustomProperty(t *testing.T) { @@ -135,6 +136,79 @@ func TestAccGithubRepositoryCustomProperty(t *testing.T) { }) }) + t.Run("updates custom property value in place without replacement", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-custom-prop-%s", testResourcePrefix, randomID) + propertyName := fmt.Sprintf("tf-acc-test-property-%s", randomID) + + configTemplate := ` + resource "github_organization_custom_properties" "test" { + allowed_values = ["alpha", "beta"] + description = "Test Description" + property_name = "%s" + value_type = "single_select" + } + resource "github_repository" "test" { + name = "%s" + auto_init = true + } + resource "github_repository_custom_property" "test" { + repository = github_repository.test.name + property_name = github_organization_custom_properties.test.property_name + property_type = github_organization_custom_properties.test.value_type + property_value = ["%s"] + } + ` + + configAlpha := fmt.Sprintf(configTemplate, propertyName, repoName, "alpha") + configBeta := fmt.Sprintf(configTemplate, propertyName, repoName, "beta") + + var firstID string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: configAlpha, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository_custom_property.test", "property_value.#", "1"), + resource.TestCheckResourceAttr("github_repository_custom_property.test", "property_value.0", "alpha"), + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["github_repository_custom_property.test"] + if !ok { + return fmt.Errorf("resource not found in state") + } + firstID = rs.Primary.ID + return nil + }, + ), + }, + { + Config: configBeta, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository_custom_property.test", "property_value.#", "1"), + resource.TestCheckResourceAttr("github_repository_custom_property.test", "property_value.0", "beta"), + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["github_repository_custom_property.test"] + if !ok { + return fmt.Errorf("resource not found in state") + } + if rs.Primary.ID != firstID { + return fmt.Errorf("resource ID changed across update: %q -> %q (expected in-place update)", firstID, rs.Primary.ID) + } + return nil + }, + ), + }, + { + Config: configBeta, + PlanOnly: true, + }, + }, + }) + }) + t.Run("creates custom property of type string without error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-custom-prop-%s", testResourcePrefix, randomID)