@@ -3,31 +3,41 @@ package github
33import (
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
1314func 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
242306func 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