Skip to content

Commit e6829dc

Browse files
committed
refactor: address PR review for github_repository_custom_properties
- Use context-aware CRUD functions (CreateContext, ReadContext, etc.) - Rename repository_name to repository, add computed repository_id field - Add CustomizeDiff with diffRepository for repo rename support - Separate Create and Update into distinct functions - Use buildID/parseID2 with : separator instead of buildTwoPartID - Replace log.Printf with tflog for structured logging - Return d.Set errors instead of swallowing them - Extract filterManagedCustomProperties to reduce cognitive complexity - Update tests to use ConfigStateChecks and extract duplicate literals - Update import format to accept repository name (owner from provider) - Update documentation to reflect schema changes
1 parent 26bf410 commit e6829dc

4 files changed

Lines changed: 184 additions & 110 deletions

github/provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func Provider() *schema.Provider {
192192
"github_repository_collaborator": resourceGithubRepositoryCollaborator(),
193193
"github_repository_collaborators": resourceGithubRepositoryCollaborators(),
194194
"github_repository_custom_property": resourceGithubRepositoryCustomProperty(),
195-
"github_repository_custom_properties": resourceGithubRepositoryCustomProperties(),
195+
"github_repository_custom_properties": resourceGithubRepositoryCustomProperties(),
196196
"github_repository_deploy_key": resourceGithubRepositoryDeployKey(),
197197
"github_repository_deployment_branch_policy": resourceGithubRepositoryDeploymentBranchPolicy(),
198198
"github_repository_environment": resourceGithubRepositoryEnvironment(),

github/resource_github_repository_custom_properties.go

Lines changed: 139 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,41 @@ package github
33
import (
44
"context"
55
"fmt"
6-
"log"
7-
"strings"
86

9-
"github.com/google/go-github/v83/github"
7+
"github.com/google/go-github/v84/github"
8+
"github.com/hashicorp/terraform-plugin-log/tflog"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
1011
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1112
)
1213

1314
func resourceGithubRepositoryCustomProperties() *schema.Resource {
1415
return &schema.Resource{
1516
Description: "Manages custom properties for a GitHub repository. This resource allows you to set multiple custom property values on a single repository in a single resource block, with in-place updates when values change.",
16-
Create: resourceGithubRepositoryCustomPropertiesCreateOrUpdate,
17-
Read: resourceGithubRepositoryCustomPropertiesRead,
18-
Update: resourceGithubRepositoryCustomPropertiesCreateOrUpdate,
19-
Delete: resourceGithubRepositoryCustomPropertiesDelete,
17+
18+
CreateContext: resourceGithubRepositoryCustomPropertiesCreate,
19+
ReadContext: resourceGithubRepositoryCustomPropertiesRead,
20+
UpdateContext: resourceGithubRepositoryCustomPropertiesUpdate,
21+
DeleteContext: resourceGithubRepositoryCustomPropertiesDelete,
2022
Importer: &schema.ResourceImporter{
2123
StateContext: resourceGithubRepositoryCustomPropertiesImport,
2224
},
2325

26+
CustomizeDiff: customdiff.All(
27+
diffRepository,
28+
),
29+
2430
Schema: map[string]*schema.Schema{
25-
"repository_name": {
31+
"repository": {
2632
Type: schema.TypeString,
2733
Required: true,
28-
ForceNew: true,
2934
Description: "Name of the repository.",
3035
},
36+
"repository_id": {
37+
Type: schema.TypeInt,
38+
Computed: true,
39+
Description: "The ID of the GitHub repository.",
40+
},
3141
"property": {
3242
Type: schema.TypeSet,
3343
Required: true,
@@ -66,15 +76,10 @@ func resourceGithubRepositoryCustomPropertiesHash(v any) int {
6676
return schema.HashString(name)
6777
}
6878

69-
func resourceGithubRepositoryCustomPropertiesCreateOrUpdate(d *schema.ResourceData, meta any) error {
70-
if err := checkOrganization(meta); err != nil {
71-
return err
72-
}
73-
79+
func resourceGithubRepositoryCustomPropertiesApply(ctx context.Context, d *schema.ResourceData, meta any) error {
7480
client := meta.(*Owner).v3client
75-
ctx := context.Background()
7681
owner := meta.(*Owner).name
77-
repoName := d.Get("repository_name").(string)
82+
repoName := d.Get("repository").(string)
7883
properties := d.Get("property").(*schema.Set).List()
7984

8085
// Get all organization custom property definitions to determine types
@@ -128,22 +133,68 @@ func resourceGithubRepositoryCustomPropertiesCreateOrUpdate(d *schema.ResourceDa
128133
return fmt.Errorf("error setting custom properties for repository %s/%s: %w", owner, repoName, err)
129134
}
130135

131-
d.SetId(buildTwoPartID(owner, repoName))
136+
return nil
137+
}
138+
139+
func resourceGithubRepositoryCustomPropertiesCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
140+
err := checkOrganization(meta)
141+
if err != nil {
142+
return diag.FromErr(err)
143+
}
132144

133-
return resourceGithubRepositoryCustomPropertiesRead(d, meta)
145+
owner := meta.(*Owner).name
146+
client := meta.(*Owner).v3client
147+
repoName := d.Get("repository").(string)
148+
149+
if err := resourceGithubRepositoryCustomPropertiesApply(ctx, d, meta); err != nil {
150+
return diag.FromErr(err)
151+
}
152+
153+
id, err := buildID(owner, repoName)
154+
if err != nil {
155+
return diag.FromErr(err)
156+
}
157+
d.SetId(id)
158+
159+
repo, _, err := client.Repositories.Get(ctx, owner, repoName)
160+
if err != nil {
161+
return diag.FromErr(err)
162+
}
163+
164+
if err := d.Set("repository_id", int(repo.GetID())); err != nil {
165+
return diag.FromErr(err)
166+
}
167+
168+
return nil
169+
}
170+
171+
func resourceGithubRepositoryCustomPropertiesUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
172+
err := checkOrganization(meta)
173+
if err != nil {
174+
return diag.FromErr(err)
175+
}
176+
177+
if err := resourceGithubRepositoryCustomPropertiesApply(ctx, d, meta); err != nil {
178+
return diag.FromErr(err)
179+
}
180+
181+
return nil
134182
}
135183

136-
func resourceGithubRepositoryCustomPropertiesRead(d *schema.ResourceData, meta any) error {
137-
if err := checkOrganization(meta); err != nil {
138-
return err
184+
func resourceGithubRepositoryCustomPropertiesRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
185+
err := checkOrganization(meta)
186+
if err != nil {
187+
return diag.FromErr(err)
139188
}
140189

190+
ctx = tflog.SetField(ctx, "id", d.Id())
191+
141192
client := meta.(*Owner).v3client
142-
ctx := context.Background()
193+
owner := meta.(*Owner).name
143194

144-
owner, repoName, err := parseTwoPartID(d.Id(), "owner", "repository")
195+
_, repoName, err := parseID2(d.Id())
145196
if err != nil {
146-
return err
197+
return diag.FromErr(err)
147198
}
148199

149200
// Get current properties from state to know which ones we're managing.
@@ -160,60 +211,73 @@ func resourceGithubRepositoryCustomPropertiesRead(d *schema.ResourceData, meta a
160211
// Read actual properties from GitHub
161212
allCustomProperties, _, err := client.Repositories.GetAllCustomPropertyValues(ctx, owner, repoName)
162213
if err != nil {
163-
return fmt.Errorf("error reading custom properties for repository %s/%s: %w", owner, repoName, err)
214+
return diag.FromErr(fmt.Errorf("error reading custom properties for repository %s/%s: %w", owner, repoName, err))
164215
}
165216

166-
// Build the property set — either all properties (import) or only managed ones
167-
managedProperties := make([]any, 0)
168-
for _, prop := range allCustomProperties {
169-
if !isImport && !managedPropertyNames[prop.PropertyName] {
217+
managedProperties, err := filterManagedCustomProperties(allCustomProperties, managedPropertyNames, isImport)
218+
if err != nil {
219+
return diag.FromErr(fmt.Errorf("error processing custom properties for repository %s/%s: %w", owner, repoName, err))
220+
}
221+
222+
// If no properties exist, remove resource from state
223+
if len(managedProperties) == 0 {
224+
tflog.Warn(ctx, "No custom properties found, removing from state", map[string]any{"owner": owner, "repository": repoName})
225+
d.SetId("")
226+
return nil
227+
}
228+
229+
if err := d.Set("repository", repoName); err != nil {
230+
return diag.FromErr(err)
231+
}
232+
if err := d.Set("property", managedProperties); err != nil {
233+
return diag.FromErr(err)
234+
}
235+
236+
return nil
237+
}
238+
239+
// filterManagedCustomProperties builds the property set from GitHub API results,
240+
// filtering to only managed properties (or all properties during import).
241+
func filterManagedCustomProperties(allProps []*github.CustomPropertyValue, managed map[string]bool, isImport bool) ([]any, error) {
242+
result := make([]any, 0)
243+
for _, prop := range allProps {
244+
if !isImport && !managed[prop.PropertyName] {
170245
continue
171246
}
172247

173-
// Skip properties with nil/null values (unset)
174248
if prop.Value == nil {
175249
continue
176250
}
177251

178252
propertyValue, err := parseRepositoryCustomPropertyValueToStringSlice(prop)
179253
if err != nil {
180-
return fmt.Errorf("error parsing property %q for repository %s/%s: %w", prop.PropertyName, owner, repoName, err)
254+
return nil, fmt.Errorf("error parsing property %q: %w", prop.PropertyName, err)
181255
}
182256

183257
if len(propertyValue) == 0 {
184258
continue
185259
}
186260

187-
managedProperties = append(managedProperties, map[string]any{
261+
result = append(result, map[string]any{
188262
"name": prop.PropertyName,
189263
"value": propertyValue,
190264
})
191265
}
192-
193-
// If no properties exist, remove resource from state
194-
if len(managedProperties) == 0 {
195-
log.Printf("[WARN] No custom properties found for %s/%s, removing from state", owner, repoName)
196-
d.SetId("")
197-
return nil
198-
}
199-
200-
_ = d.Set("repository_name", repoName)
201-
_ = d.Set("property", managedProperties)
202-
203-
return nil
266+
return result, nil
204267
}
205268

206-
func resourceGithubRepositoryCustomPropertiesDelete(d *schema.ResourceData, meta any) error {
207-
if err := checkOrganization(meta); err != nil {
208-
return err
269+
func resourceGithubRepositoryCustomPropertiesDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
270+
err := checkOrganization(meta)
271+
if err != nil {
272+
return diag.FromErr(err)
209273
}
210274

211275
client := meta.(*Owner).v3client
212-
ctx := context.Background()
276+
owner := meta.(*Owner).name
213277

214-
owner, repoName, err := parseTwoPartID(d.Id(), "owner", "repository")
278+
_, repoName, err := parseID2(d.Id())
215279
if err != nil {
216-
return err
280+
return diag.FromErr(err)
217281
}
218282

219283
properties := d.Get("property").(*schema.Set).List()
@@ -233,20 +297,38 @@ func resourceGithubRepositoryCustomPropertiesDelete(d *schema.ResourceData, meta
233297

234298
_, err = client.Repositories.CreateOrUpdateCustomProperties(ctx, owner, repoName, customProperties)
235299
if err != nil {
236-
return fmt.Errorf("error deleting custom properties for repository %s/%s: %w", owner, repoName, err)
300+
return diag.FromErr(fmt.Errorf("error deleting custom properties for repository %s/%s: %w", owner, repoName, err))
237301
}
238302

239303
return nil
240304
}
241305

242306
func resourceGithubRepositoryCustomPropertiesImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
243-
// Import ID format: owner/repo (using standard two-part ID)
244-
// On import, Read will detect empty state and import ALL properties
245-
parts := strings.SplitN(d.Id(), "/", 2)
246-
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
247-
return nil, fmt.Errorf("invalid import ID %q, expected format: owner/repository", d.Id())
307+
// Import ID format: <repository> — owner is inferred from the provider config.
308+
// On import, Read will detect empty state and import ALL properties.
309+
repoName := d.Id()
310+
311+
owner := meta.(*Owner).name
312+
client := meta.(*Owner).v3client
313+
314+
id, err := buildID(owner, repoName)
315+
if err != nil {
316+
return nil, err
317+
}
318+
d.SetId(id)
319+
320+
if err := d.Set("repository", repoName); err != nil {
321+
return nil, err
322+
}
323+
324+
repo, _, err := client.Repositories.Get(ctx, owner, repoName)
325+
if err != nil {
326+
return nil, fmt.Errorf("failed to retrieve repository %s: %w", repoName, err)
327+
}
328+
329+
if err := d.Set("repository_id", int(repo.GetID())); err != nil {
330+
return nil, err
248331
}
249332

250-
d.SetId(buildTwoPartID(parts[0], parts[1]))
251333
return []*schema.ResourceData{d}, nil
252334
}

0 commit comments

Comments
 (0)