Skip to content

feat: add github_repository_custom_properties resource for batch property management#3237

Open
mkushakov wants to merge 2 commits intointegrations:mainfrom
mkushakov:feat/repository-custom-properties-resource
Open

feat: add github_repository_custom_properties resource for batch property management#3237
mkushakov wants to merge 2 commits intointegrations:mainfrom
mkushakov:feat/repository-custom-properties-resource

Conversation

@mkushakov
Copy link

@mkushakov mkushakov commented Feb 27, 2026

Resolves #3240

Summary

Add a new github_repository_custom_properties resource (plural) that manages multiple custom property values on a single repository in one resource block, with in-place updates when values change.

This complements the existing github_repository_custom_property resource (singular), which manages one property per resource instance and requires destroy+recreate on any change.

Motivation

The existing singular github_repository_custom_property resource has limitations:

  • All fields are ForceNew — changing a property value destroys and recreates the resource, even though the GitHub API supports PATCH
  • One resource per property — managing 10 properties on a repo requires 10 resource blocks
  • Requires property_type — the user must manually specify the property type, even though it's already defined at the org level
  • One API call per property — no batching

The new plural resource addresses all of these:

Capability Singular (github_repository_custom_property) Plural (github_repository_custom_properties)
Properties per resource 1 N
Value change behavior Destroy + recreate In-place update
Requires property_type Yes No (auto-detected from org definitions)
API calls 1 per property 1 for all properties (batch)
Import owner/repo/property_name owner/repo (imports ALL properties)

Changes

New Resource (resource_github_repository_custom_properties.go)

  • Schema with repository_name (ForceNew) and property (TypeSet of name/value blocks)
  • Create and Update share the same function — looks up org property definitions to auto-detect types, then calls CreateOrUpdateCustomProperties with all properties in a single API call
  • Read filters to only managed properties (from state), or imports ALL properties when state is empty (import case)
  • Delete sets all managed properties to nil in a single API call
  • Custom StateContext importer that accepts owner/repo format and imports all properties
  • Hash function based on property name only (not values), so value changes are detected as in-place modifications
  • checkOrganization guard on all CRUD operations
  • Proper error wrapping with fmt.Errorf and %w

Provider Registration (provider.go)

  • Registered as github_repository_custom_properties

Tests (resource_github_repository_custom_properties_test.go)

  • Creates and reads multiple properties — sets two properties (single_select + string) and verifies
  • Updates property value in place — changes a single_select value from "production" to "staging" without destroy
  • Imports all properties — creates properties, then imports via owner/repo
  • Creates multi_select property — verifies multi-value property handling

All tests use providerFactories (modern pattern) and skipUnlessHasOrgs.

Example Usage

resource "github_repository_custom_properties" "example" {
  repository_name = github_repository.example.name

  property {
    name  = "environment"
    value = ["production"]
  }

  property {
    name  = "team"
    value = ["platform"]
  }

  property {
    name  = "languages"
    value = ["go", "typescript"]  # multi_select
  }
}

Relationship to Existing Resources

  • github_repository_custom_property (singular) — unchanged, still available. Users can choose whichever pattern fits their workflow.
  • github_organization_custom_properties — defines properties at the org level. The new resource reads these definitions to auto-detect property types.
  • data.github_repository_custom_properties (data source) — already exists, reads all properties for a repo.

Note: Using both singular and plural resources on the same repository and property is not recommended, as they would conflict. Users should choose one pattern per repository.

Testing

  • go build ./... — passes
  • go vet ./... — passes

New resource for managing multiple custom property values on a repository in a single resource block with in-place updates. Complements the existing singular github_repository_custom_property resource.
@github-actions
Copy link

👋 Hi! Thank you for this contribution! Just to let you know, our GitHub SDK team does a round of issue and PR reviews twice a week, every Monday and Friday! We have a process in place for prioritizing and responding to your input. Because you are a part of this community please feel free to comment, add to, or pick up any issues/PRs that are labeled with Status: Up for grabs. You & others like you are the reason all of this works! So thank you & happy coding! 🚀

@github-actions github-actions bot added the Type: Feature New feature or request label Feb 27, 2026
@mkushakov
Copy link
Author

@deiga / @stevehipwell , any chances for reviewing this PR?

@deiga
Copy link
Collaborator

deiga commented Mar 13, 2026

@mkushakov please don't ping us multiple times.
We have our own lives and work, OSS is usually a best effort situation when there is capacity for it.

If you currently have capacity for these PRs maybe you could review the last few merged PRs to look at what are things we usually ask for?

@mkushakov
Copy link
Author

@mkushakov please don't ping us multiple times. We have our own lives and work, OSS is usually a best effort situation when there is capacity for it.

If you currently have capacity for these PRs maybe you could review the last few merged PRs to look at what are things we usually ask for?

sorry, don't want to be PITA, just want to see what is a way forward to backport most important changes from our fork so we can move back to this upstream
also i know there are a many people who also will benefit from this fixes/features

Copy link
Collaborator

@deiga deiga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

Comment on lines +16 to +19
Create: resourceGithubRepositoryCustomPropertiesCreateOrUpdate,
Read: resourceGithubRepositoryCustomPropertiesRead,
Update: resourceGithubRepositoryCustomPropertiesCreateOrUpdate,
Delete: resourceGithubRepositoryCustomPropertiesDelete,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Context-aware functions

Comment on lines +25 to +30
"repository_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Name of the repository.",
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change to "repository", add "repository_id" field and add diffRepository customizediff to support repo renames

return schema.HashString(name)
}

func resourceGithubRepositoryCustomPropertiesCreateOrUpdate(d *schema.ResourceData, meta any) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please separate Create and Update functions

return fmt.Errorf("error setting custom properties for repository %s/%s: %w", owner, repoName, err)
}

d.SetId(buildTwoPartID(owner, repoName))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use buildID


d.SetId(buildTwoPartID(owner, repoName))

return resourceGithubRepositoryCustomPropertiesRead(d, meta)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't call CRUD functions directly. If there are computed values to be set, set them here


// If no properties exist, remove resource from state
if len(managedProperties) == 0 {
log.Printf("[WARN] No custom properties found for %s/%s, removing from state", owner, repoName)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use tflog

Comment on lines +200 to +201
_ = d.Set("repository_name", repoName)
_ = d.Set("property", managedProperties)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't swallow errors

client := meta.(*Owner).v3client
ctx := context.Background()

owner, repoName, err := parseTwoPartID(d.Id(), "owner", "repository")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pleas use parseID2

Comment on lines +245 to +248
parts := strings.SplitN(d.Id(), "/", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return nil, fmt.Errorf("invalid import ID %q, expected format: owner/repository", d.Id())
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change import format to use : separator. Then you can use parseID2

Steps: []resource.TestStep{
{
Config: config,
Check: check,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor to use ConfigStsteChecks and inline the variable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: add github_repository_custom_properties resource for batch property management

2 participants