Skip to content

Commit 505bbfe

Browse files
committed
feat: Add custom_properties to github_repository and github_enterprise_custom_property resource (#3230)
Allow custom properties to be set on repositories at creation time, fixing 422 errors when an organization enforces required custom properties. Also adds a new github_enterprise_custom_property resource for managing custom property definitions at the enterprise level. Closes #3230
1 parent 94f71cf commit 505bbfe

6 files changed

Lines changed: 571 additions & 0 deletions

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ func Provider() *schema.Provider {
217217
"github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(),
218218
"github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(),
219219
"github_enterprise_security_analysis_settings": resourceGithubEnterpriseSecurityAnalysisSettings(),
220+
"github_enterprise_custom_property": resourceGithubEnterpriseCustomProperties(),
220221
"github_workflow_repository_permissions": resourceGithubWorkflowRepositoryPermissions(),
221222
},
222223

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"log"
6+
"net/http"
7+
8+
"github.com/google/go-github/v84/github"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
11+
)
12+
13+
func resourceGithubEnterpriseCustomProperties() *schema.Resource {
14+
return &schema.Resource{
15+
Create: resourceGithubEnterpriseCustomPropertiesCreate,
16+
Read: resourceGithubEnterpriseCustomPropertiesRead,
17+
Update: resourceGithubEnterpriseCustomPropertiesUpdate,
18+
Delete: resourceGithubEnterpriseCustomPropertiesDelete,
19+
Importer: &schema.ResourceImporter{
20+
State: resourceGithubEnterpriseCustomPropertiesImport,
21+
},
22+
23+
Schema: map[string]*schema.Schema{
24+
"enterprise_slug": {
25+
Type: schema.TypeString,
26+
Required: true,
27+
ForceNew: true,
28+
Description: "The slug of the enterprise.",
29+
},
30+
"property_name": {
31+
Type: schema.TypeString,
32+
Required: true,
33+
ForceNew: true,
34+
Description: "The name of the custom property.",
35+
},
36+
"value_type": {
37+
Type: schema.TypeString,
38+
Required: true,
39+
Description: "The type of the value for the property. Can be one of: 'string', 'single_select', 'multi_select', 'true_false', 'url'.",
40+
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{string(github.PropertyValueTypeString), string(github.PropertyValueTypeSingleSelect), string(github.PropertyValueTypeMultiSelect), string(github.PropertyValueTypeTrueFalse), string(github.PropertyValueTypeURL)}, false)),
41+
},
42+
"required": {
43+
Type: schema.TypeBool,
44+
Optional: true,
45+
Description: "Whether the custom property is required.",
46+
},
47+
"default_value": {
48+
Type: schema.TypeString,
49+
Optional: true,
50+
Computed: true,
51+
Description: "The default value of the custom property.",
52+
},
53+
"description": {
54+
Type: schema.TypeString,
55+
Optional: true,
56+
Computed: true,
57+
Description: "A short description of the custom property.",
58+
},
59+
"allowed_values": {
60+
Type: schema.TypeList,
61+
Optional: true,
62+
Computed: true,
63+
Description: "An ordered list of allowed values for the property. Only applicable to 'single_select' and 'multi_select' types.",
64+
Elem: &schema.Schema{Type: schema.TypeString},
65+
},
66+
"values_editable_by": {
67+
Type: schema.TypeString,
68+
Optional: true,
69+
Computed: true,
70+
Description: "Who can edit the values of the property. Can be one of: 'org_actors', 'org_and_repo_actors'. Defaults to 'org_actors'.",
71+
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"org_actors", "org_and_repo_actors"}, false)),
72+
},
73+
},
74+
}
75+
}
76+
77+
func resourceGithubEnterpriseCustomPropertiesCreate(d *schema.ResourceData, meta any) error {
78+
client := meta.(*Owner).v3client
79+
ctx := context.Background()
80+
81+
enterpriseSlug := d.Get("enterprise_slug").(string)
82+
propertyName := d.Get("property_name").(string)
83+
84+
property := buildEnterpriseCustomProperty(d)
85+
86+
_, _, err := client.Enterprise.CreateOrUpdateCustomProperty(ctx, enterpriseSlug, propertyName, property)
87+
if err != nil {
88+
return err
89+
}
90+
91+
d.SetId(buildTwoPartID(enterpriseSlug, propertyName))
92+
return resourceGithubEnterpriseCustomPropertiesRead(d, meta)
93+
}
94+
95+
func resourceGithubEnterpriseCustomPropertiesRead(d *schema.ResourceData, meta any) error {
96+
client := meta.(*Owner).v3client
97+
ctx := context.Background()
98+
99+
enterpriseSlug, propertyName, err := parseTwoPartID(d.Id(), "enterprise_slug", "property_name")
100+
if err != nil {
101+
return err
102+
}
103+
104+
property, resp, err := client.Enterprise.GetCustomProperty(ctx, enterpriseSlug, propertyName)
105+
if err != nil {
106+
if resp != nil && resp.StatusCode == http.StatusNotFound {
107+
log.Printf("[INFO] Removing enterprise custom property %s/%s from state because it no longer exists", enterpriseSlug, propertyName)
108+
d.SetId("")
109+
return nil
110+
}
111+
return err
112+
}
113+
114+
defaultValue, _ := property.DefaultValueString()
115+
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())
125+
126+
return nil
127+
}
128+
129+
func resourceGithubEnterpriseCustomPropertiesUpdate(d *schema.ResourceData, meta any) error {
130+
client := meta.(*Owner).v3client
131+
ctx := context.Background()
132+
133+
enterpriseSlug, propertyName, err := parseTwoPartID(d.Id(), "enterprise_slug", "property_name")
134+
if err != nil {
135+
return err
136+
}
137+
138+
property := buildEnterpriseCustomProperty(d)
139+
140+
_, _, err = client.Enterprise.CreateOrUpdateCustomProperty(ctx, enterpriseSlug, propertyName, property)
141+
if err != nil {
142+
return err
143+
}
144+
145+
return resourceGithubEnterpriseCustomPropertiesRead(d, meta)
146+
}
147+
148+
func resourceGithubEnterpriseCustomPropertiesDelete(d *schema.ResourceData, meta any) error {
149+
client := meta.(*Owner).v3client
150+
ctx := context.Background()
151+
152+
enterpriseSlug, propertyName, err := parseTwoPartID(d.Id(), "enterprise_slug", "property_name")
153+
if err != nil {
154+
return err
155+
}
156+
157+
_, err = client.Enterprise.RemoveCustomProperty(ctx, enterpriseSlug, propertyName)
158+
return err
159+
}
160+
161+
func resourceGithubEnterpriseCustomPropertiesImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
162+
enterpriseSlug, propertyName, err := parseTwoPartID(d.Id(), "enterprise_slug", "property_name")
163+
if err != nil {
164+
return nil, err
165+
}
166+
167+
_ = d.Set("enterprise_slug", enterpriseSlug)
168+
_ = d.Set("property_name", propertyName)
169+
170+
return []*schema.ResourceData{d}, nil
171+
}
172+
173+
func buildEnterpriseCustomProperty(d *schema.ResourceData) *github.CustomProperty {
174+
propertyName := d.Get("property_name").(string)
175+
valueType := github.PropertyValueType(d.Get("value_type").(string))
176+
required := d.Get("required").(bool)
177+
defaultValue := d.Get("default_value").(string)
178+
description := d.Get("description").(string)
179+
180+
rawAllowedValues := d.Get("allowed_values").([]any)
181+
allowedValues := make([]string, 0, len(rawAllowedValues))
182+
for _, v := range rawAllowedValues {
183+
allowedValues = append(allowedValues, v.(string))
184+
}
185+
186+
property := &github.CustomProperty{
187+
PropertyName: &propertyName,
188+
ValueType: valueType,
189+
Required: &required,
190+
DefaultValue: &defaultValue,
191+
Description: &description,
192+
AllowedValues: allowedValues,
193+
}
194+
195+
if val, ok := d.GetOk("values_editable_by"); ok {
196+
str := val.(string)
197+
property.ValuesEditableBy = &str
198+
}
199+
200+
return property
201+
}

0 commit comments

Comments
 (0)