Skip to content

Commit 9efa7f0

Browse files
committed
feat: Add custom_properties to github_repository and github_enterprise_custom_property resource (#3230, #3304)
Allow custom properties to be set on repositories at creation time, fixing 422 errors when an organization enforces required custom properties. Adds a new github_enterprise_custom_property resource and data source for managing custom property definitions at the enterprise level. Uses context-aware CRUD functions, proper 404 handling, and ConfigStateChecks in acceptance tests per maintainer guidelines. Closes #3230, #3304
1 parent 505bbfe commit 9efa7f0

6 files changed

Lines changed: 302 additions & 123 deletions
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package github
2+
3+
import (
4+
"context"
5+
6+
"github.com/google/go-github/v84/github"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
)
10+
11+
func dataSourceGithubEnterpriseCustomProperty() *schema.Resource {
12+
return &schema.Resource{
13+
Description: "Use this data source to retrieve information about a custom property definition for a GitHub enterprise.",
14+
15+
ReadContext: dataSourceGithubEnterpriseCustomPropertyRead,
16+
17+
Schema: map[string]*schema.Schema{
18+
"enterprise_slug": {
19+
Type: schema.TypeString,
20+
Required: true,
21+
Description: "The slug of the enterprise.",
22+
},
23+
"property_name": {
24+
Type: schema.TypeString,
25+
Required: true,
26+
Description: "The name of the custom property.",
27+
},
28+
"value_type": {
29+
Type: schema.TypeString,
30+
Computed: true,
31+
Description: "The type of the value for the property.",
32+
},
33+
"required": {
34+
Type: schema.TypeBool,
35+
Computed: true,
36+
Description: "Whether the custom property is required.",
37+
},
38+
"default_values": {
39+
Type: schema.TypeList,
40+
Computed: true,
41+
Description: "The default value(s) of the custom property.",
42+
Elem: &schema.Schema{Type: schema.TypeString},
43+
},
44+
"description": {
45+
Type: schema.TypeString,
46+
Computed: true,
47+
Description: "A short description of the custom property.",
48+
},
49+
"allowed_values": {
50+
Type: schema.TypeList,
51+
Computed: true,
52+
Description: "An ordered list of allowed values for the property.",
53+
Elem: &schema.Schema{Type: schema.TypeString},
54+
},
55+
"values_editable_by": {
56+
Type: schema.TypeString,
57+
Computed: true,
58+
Description: "Who can edit the values of the property. Can be one of 'org_actors' or 'org_and_repo_actors'.",
59+
},
60+
},
61+
}
62+
}
63+
64+
func dataSourceGithubEnterpriseCustomPropertyRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
65+
client := meta.(*Owner).v3client
66+
67+
enterpriseSlug := d.Get("enterprise_slug").(string)
68+
propertyName := d.Get("property_name").(string)
69+
70+
property, _, err := client.Enterprise.GetCustomProperty(ctx, enterpriseSlug, propertyName)
71+
if err != nil {
72+
return diag.Errorf("error reading enterprise custom property %s/%s: %v", enterpriseSlug, propertyName, err)
73+
}
74+
75+
var defaultValues []string
76+
if property.ValueType == github.PropertyValueTypeMultiSelect {
77+
if vals, ok := property.DefaultValueStrings(); ok {
78+
defaultValues = vals
79+
}
80+
} else {
81+
if val, ok := property.DefaultValueString(); ok {
82+
defaultValues = []string{val}
83+
}
84+
}
85+
86+
d.SetId(buildTwoPartID(enterpriseSlug, propertyName))
87+
88+
if err := d.Set("enterprise_slug", enterpriseSlug); err != nil {
89+
return diag.FromErr(err)
90+
}
91+
if err := d.Set("property_name", property.GetPropertyName()); err != nil {
92+
return diag.FromErr(err)
93+
}
94+
if err := d.Set("value_type", string(property.ValueType)); err != nil {
95+
return diag.FromErr(err)
96+
}
97+
if err := d.Set("required", property.GetRequired()); err != nil {
98+
return diag.FromErr(err)
99+
}
100+
if err := d.Set("default_values", defaultValues); err != nil {
101+
return diag.FromErr(err)
102+
}
103+
if err := d.Set("description", property.GetDescription()); err != nil {
104+
return diag.FromErr(err)
105+
}
106+
if err := d.Set("allowed_values", property.AllowedValues); err != nil {
107+
return diag.FromErr(err)
108+
}
109+
if err := d.Set("values_editable_by", property.GetValuesEditableBy()); err != nil {
110+
return diag.FromErr(err)
111+
}
112+
113+
return nil
114+
}

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ func Provider() *schema.Provider {
295295
"github_user_external_identity": dataSourceGithubUserExternalIdentity(),
296296
"github_users": dataSourceGithubUsers(),
297297
"github_enterprise": dataSourceGithubEnterprise(),
298+
"github_enterprise_custom_property": dataSourceGithubEnterpriseCustomProperty(),
298299
"github_repository_environment_deployment_policies": dataSourceGithubRepositoryEnvironmentDeploymentPolicies(),
299300
},
300301
}

github/resource_github_enterprise_custom_properties.go

Lines changed: 84 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ import (
66
"net/http"
77

88
"github.com/google/go-github/v84/github"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
910
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1011
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1112
)
1213

1314
func resourceGithubEnterpriseCustomProperties() *schema.Resource {
1415
return &schema.Resource{
15-
Create: resourceGithubEnterpriseCustomPropertiesCreate,
16-
Read: resourceGithubEnterpriseCustomPropertiesRead,
17-
Update: resourceGithubEnterpriseCustomPropertiesUpdate,
18-
Delete: resourceGithubEnterpriseCustomPropertiesDelete,
16+
CreateContext: resourceGithubEnterpriseCustomPropertiesCreate,
17+
ReadContext: resourceGithubEnterpriseCustomPropertiesRead,
18+
UpdateContext: resourceGithubEnterpriseCustomPropertiesUpdate,
19+
DeleteContext: resourceGithubEnterpriseCustomPropertiesDelete,
1920
Importer: &schema.ResourceImporter{
20-
State: resourceGithubEnterpriseCustomPropertiesImport,
21+
StateContext: schema.ImportStatePassthroughContext,
2122
},
2223

2324
Schema: map[string]*schema.Schema{
@@ -44,16 +45,16 @@ func resourceGithubEnterpriseCustomProperties() *schema.Resource {
4445
Optional: true,
4546
Description: "Whether the custom property is required.",
4647
},
47-
"default_value": {
48-
Type: schema.TypeString,
48+
"default_values": {
49+
Type: schema.TypeList,
4950
Optional: true,
5051
Computed: true,
51-
Description: "The default value of the custom property.",
52+
Description: "The default value(s) of the custom property. For 'multi_select' properties, multiple values may be specified. For all other types, provide a single value.",
53+
Elem: &schema.Schema{Type: schema.TypeString},
5254
},
5355
"description": {
5456
Type: schema.TypeString,
5557
Optional: true,
56-
Computed: true,
5758
Description: "A short description of the custom property.",
5859
},
5960
"allowed_values": {
@@ -74,9 +75,8 @@ func resourceGithubEnterpriseCustomProperties() *schema.Resource {
7475
}
7576
}
7677

77-
func resourceGithubEnterpriseCustomPropertiesCreate(d *schema.ResourceData, meta any) error {
78+
func resourceGithubEnterpriseCustomPropertiesCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
7879
client := meta.(*Owner).v3client
79-
ctx := context.Background()
8080

8181
enterpriseSlug := d.Get("enterprise_slug").(string)
8282
propertyName := d.Get("property_name").(string)
@@ -85,20 +85,19 @@ func resourceGithubEnterpriseCustomPropertiesCreate(d *schema.ResourceData, meta
8585

8686
_, _, err := client.Enterprise.CreateOrUpdateCustomProperty(ctx, enterpriseSlug, propertyName, property)
8787
if err != nil {
88-
return err
88+
return diag.FromErr(err)
8989
}
9090

9191
d.SetId(buildTwoPartID(enterpriseSlug, propertyName))
92-
return resourceGithubEnterpriseCustomPropertiesRead(d, meta)
92+
return resourceGithubEnterpriseCustomPropertiesRead(ctx, d, meta)
9393
}
9494

95-
func resourceGithubEnterpriseCustomPropertiesRead(d *schema.ResourceData, meta any) error {
95+
func resourceGithubEnterpriseCustomPropertiesRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
9696
client := meta.(*Owner).v3client
97-
ctx := context.Background()
9897

9998
enterpriseSlug, propertyName, err := parseTwoPartID(d.Id(), "enterprise_slug", "property_name")
10099
if err != nil {
101-
return err
100+
return diag.FromErr(err)
102101
}
103102

104103
property, resp, err := client.Enterprise.GetCustomProperty(ctx, enterpriseSlug, propertyName)
@@ -108,54 +107,83 @@ func resourceGithubEnterpriseCustomPropertiesRead(d *schema.ResourceData, meta a
108107
d.SetId("")
109108
return nil
110109
}
111-
return err
110+
return diag.FromErr(err)
112111
}
113112

114-
defaultValue, _ := property.DefaultValueString()
113+
if err := d.Set("enterprise_slug", enterpriseSlug); err != nil {
114+
return diag.FromErr(err)
115+
}
116+
if err := d.Set("property_name", property.GetPropertyName()); err != nil {
117+
return diag.FromErr(err)
118+
}
119+
if err := d.Set("value_type", string(property.ValueType)); err != nil {
120+
return diag.FromErr(err)
121+
}
122+
if err := d.Set("required", property.GetRequired()); err != nil {
123+
return diag.FromErr(err)
124+
}
115125

116-
d.SetId(buildTwoPartID(enterpriseSlug, propertyName))
117-
_ = d.Set("enterprise_slug", enterpriseSlug)
118-
_ = d.Set("property_name", property.GetPropertyName())
119-
_ = d.Set("value_type", string(property.ValueType))
120-
_ = d.Set("required", property.GetRequired())
121-
_ = d.Set("default_value", defaultValue)
122-
_ = d.Set("description", property.GetDescription())
123-
_ = d.Set("allowed_values", property.AllowedValues)
124-
_ = d.Set("values_editable_by", property.GetValuesEditableBy())
126+
var defaultValues []string
127+
if property.ValueType == github.PropertyValueTypeMultiSelect {
128+
if vals, ok := property.DefaultValueStrings(); ok {
129+
defaultValues = vals
130+
}
131+
} else {
132+
if val, ok := property.DefaultValueString(); ok {
133+
defaultValues = []string{val}
134+
}
135+
}
136+
if err := d.Set("default_values", defaultValues); err != nil {
137+
return diag.FromErr(err)
138+
}
139+
if err := d.Set("description", property.GetDescription()); err != nil {
140+
return diag.FromErr(err)
141+
}
142+
if err := d.Set("allowed_values", property.AllowedValues); err != nil {
143+
return diag.FromErr(err)
144+
}
145+
if err := d.Set("values_editable_by", property.GetValuesEditableBy()); err != nil {
146+
return diag.FromErr(err)
147+
}
125148

126149
return nil
127150
}
128151

129-
func resourceGithubEnterpriseCustomPropertiesUpdate(d *schema.ResourceData, meta any) error {
152+
func resourceGithubEnterpriseCustomPropertiesUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
130153
client := meta.(*Owner).v3client
131-
ctx := context.Background()
132154

133155
enterpriseSlug, propertyName, err := parseTwoPartID(d.Id(), "enterprise_slug", "property_name")
134156
if err != nil {
135-
return err
157+
return diag.FromErr(err)
136158
}
137159

138160
property := buildEnterpriseCustomProperty(d)
139161

140162
_, _, err = client.Enterprise.CreateOrUpdateCustomProperty(ctx, enterpriseSlug, propertyName, property)
141163
if err != nil {
142-
return err
164+
return diag.FromErr(err)
143165
}
144166

145-
return resourceGithubEnterpriseCustomPropertiesRead(d, meta)
167+
return resourceGithubEnterpriseCustomPropertiesRead(ctx, d, meta)
146168
}
147169

148-
func resourceGithubEnterpriseCustomPropertiesDelete(d *schema.ResourceData, meta any) error {
170+
func resourceGithubEnterpriseCustomPropertiesDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
149171
client := meta.(*Owner).v3client
150-
ctx := context.Background()
151172

152173
enterpriseSlug, propertyName, err := parseTwoPartID(d.Id(), "enterprise_slug", "property_name")
153174
if err != nil {
154-
return err
175+
return diag.FromErr(err)
155176
}
156177

157-
_, err = client.Enterprise.RemoveCustomProperty(ctx, enterpriseSlug, propertyName)
158-
return err
178+
resp, err := client.Enterprise.RemoveCustomProperty(ctx, enterpriseSlug, propertyName)
179+
if err != nil {
180+
if resp != nil && resp.StatusCode == http.StatusNotFound {
181+
return nil
182+
}
183+
return diag.FromErr(err)
184+
}
185+
186+
return nil
159187
}
160188

161189
func resourceGithubEnterpriseCustomPropertiesImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
@@ -164,8 +192,12 @@ func resourceGithubEnterpriseCustomPropertiesImport(d *schema.ResourceData, meta
164192
return nil, err
165193
}
166194

167-
_ = d.Set("enterprise_slug", enterpriseSlug)
168-
_ = d.Set("property_name", propertyName)
195+
if err := d.Set("enterprise_slug", enterpriseSlug); err != nil {
196+
return nil, err
197+
}
198+
if err := d.Set("property_name", propertyName); err != nil {
199+
return nil, err
200+
}
169201

170202
return []*schema.ResourceData{d}, nil
171203
}
@@ -174,7 +206,6 @@ func buildEnterpriseCustomProperty(d *schema.ResourceData) *github.CustomPropert
174206
propertyName := d.Get("property_name").(string)
175207
valueType := github.PropertyValueType(d.Get("value_type").(string))
176208
required := d.Get("required").(bool)
177-
defaultValue := d.Get("default_value").(string)
178209
description := d.Get("description").(string)
179210

180211
rawAllowedValues := d.Get("allowed_values").([]any)
@@ -187,11 +218,23 @@ func buildEnterpriseCustomProperty(d *schema.ResourceData) *github.CustomPropert
187218
PropertyName: &propertyName,
188219
ValueType: valueType,
189220
Required: &required,
190-
DefaultValue: &defaultValue,
191221
Description: &description,
192222
AllowedValues: allowedValues,
193223
}
194224

225+
rawDefaultValues := d.Get("default_values").([]any)
226+
defaultValues := make([]string, 0, len(rawDefaultValues))
227+
for _, v := range rawDefaultValues {
228+
defaultValues = append(defaultValues, v.(string))
229+
}
230+
if len(defaultValues) > 0 {
231+
if valueType == github.PropertyValueTypeMultiSelect {
232+
property.DefaultValue = defaultValues
233+
} else {
234+
property.DefaultValue = defaultValues[0]
235+
}
236+
}
237+
195238
if val, ok := d.GetOk("values_editable_by"); ok {
196239
str := val.(string)
197240
property.ValuesEditableBy = &str

0 commit comments

Comments
 (0)