Skip to content

Commit 1ca7092

Browse files
felixlutfelixlutkfcampbell
authored
feat: support repository level custom_property resource and custom_properties datasource (#2316)
* add octokit sdk client * stash half working solution * add repository custom properties data source * break out custom props parsing logic to its own function * use background ctx * format provider.go * fix error msg to include repoName instead of its pointer * use type switch instead of if else * fix linting errors * restructure datasource to take a property name and call it github_repository_custom_property instead * formatting * rename file to match datasource name * implement data_source_github_repository_custom_property with go-github * implement resource_github_repository_custom_property * update descriptions * remove custom_property resource in favour of custom_propertIES one * add custom_property resource to provider.go * formatting * add tests for repository_custom_property * update description of test * add tests for each custom_property type * rollback repo changes * add tests for custom_property datasource * formatting * breakout parsing custom_property_value as a string slice to its own function * bump go-github to v66 for new files * add property_type as a required attribute * flip datasource to use typeList instead of typeSet * refactor tests for data source to use property_type * Update data_source_github_repository_custom_properties.go * cleanup incorrect comments * remove old comment * add docs * fix typo --------- Co-authored-by: felixlut <[email protected]> Co-authored-by: Keegan Campbell <[email protected]>
1 parent 0419c6b commit 1ca7092

7 files changed

+788
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/google/go-github/v66/github"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
)
10+
11+
func dataSourceGithubRepositoryCustomProperties() *schema.Resource {
12+
return &schema.Resource{
13+
Read: dataSourceGithubOrgaRepositoryCustomProperties,
14+
15+
Schema: map[string]*schema.Schema{
16+
"repository": {
17+
Type: schema.TypeString,
18+
Required: true,
19+
Description: "Name of the repository which the custom properties should be on.",
20+
},
21+
"property": {
22+
Type: schema.TypeSet,
23+
Computed: true,
24+
Description: "List of custom properties",
25+
Elem: &schema.Resource{
26+
Schema: map[string]*schema.Schema{
27+
"property_name": {
28+
Type: schema.TypeString,
29+
Computed: true,
30+
Description: "Name of the custom property.",
31+
},
32+
"property_value": {
33+
Type: schema.TypeSet,
34+
Computed: true,
35+
Description: "Value of the custom property.",
36+
Elem: &schema.Schema{
37+
Type: schema.TypeString,
38+
},
39+
},
40+
},
41+
},
42+
},
43+
},
44+
}
45+
}
46+
47+
func dataSourceGithubOrgaRepositoryCustomProperties(d *schema.ResourceData, meta interface{}) error {
48+
49+
client := meta.(*Owner).v3client
50+
ctx := context.Background()
51+
52+
owner := meta.(*Owner).name
53+
54+
repoName := d.Get("repository").(string)
55+
56+
allCustomProperties, _, err := client.Repositories.GetAllCustomPropertyValues(ctx, owner, repoName)
57+
if err != nil {
58+
return err
59+
}
60+
61+
results, err := flattenRepositoryCustomProperties(allCustomProperties)
62+
if err != nil {
63+
return err
64+
}
65+
66+
d.SetId(buildTwoPartID(owner, repoName))
67+
d.Set("repository", repoName)
68+
d.Set("property", results)
69+
70+
return nil
71+
}
72+
73+
func flattenRepositoryCustomProperties(customProperties []*github.CustomPropertyValue) ([]interface{}, error) {
74+
75+
results := make([]interface{}, 0)
76+
for _, prop := range customProperties {
77+
result := make(map[string]interface{})
78+
79+
result["property_name"] = prop.PropertyName
80+
81+
propertyValue, err := parseRepositoryCustomPropertyValueToStringSlice(prop)
82+
if err != nil {
83+
return nil, err
84+
}
85+
86+
result["property_value"] = propertyValue
87+
88+
results = append(results, result)
89+
}
90+
91+
return results, nil
92+
}
93+
94+
func parseRepositoryCustomPropertyValueToStringSlice(prop *github.CustomPropertyValue) ([]string, error) {
95+
switch value := prop.Value.(type) {
96+
case string:
97+
return []string{value}, nil
98+
case []string:
99+
return value, nil
100+
default:
101+
return nil, fmt.Errorf("custom property value couldn't be parsed as a string or a list of strings: %s", value)
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package github
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
9+
)
10+
11+
func TestAccGithubRepositoryCustomPropertiesDataSource(t *testing.T) {
12+
13+
t.Skip("You need an org with custom properties already setup as described in the variables below") // TODO: at the time of writing org_custom_properties are not supported by this terraform provider, so cant be setup in the test itself for now
14+
singleSelectPropertyName := "single-select" // Needs to be a of type single_select, and have "option1" as an option
15+
multiSelectPropertyName := "multi-select" // Needs to be a of type multi_select, and have "option1" and "option2" as an options
16+
trueFlasePropertyName := "true-false" // Needs to be a of type true_false, and have "option1" as an option
17+
stringPropertyName := "string" // Needs to be a of type string, and have "option1" as an option
18+
19+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
20+
21+
t.Run("creates custom property of type single_select without error", func(t *testing.T) {
22+
23+
config := fmt.Sprintf(`
24+
resource "github_repository" "test" {
25+
name = "tf-acc-test-%s"
26+
auto_init = true
27+
}
28+
resource "github_repository_custom_property" "test" {
29+
repository = github_repository.test.name
30+
property_name = "%s"
31+
property_type = "single_select"
32+
property_value = ["option1"]
33+
}
34+
data "github_repository_custom_properties" "test" {
35+
repository = github_repository_custom_property.test.repository
36+
}
37+
`, randomID, singleSelectPropertyName)
38+
39+
check := resource.ComposeTestCheckFunc(
40+
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
41+
"property.*", map[string]string{
42+
"property_name": singleSelectPropertyName,
43+
"property_value.#": "1",
44+
"property_value.0": "option1",
45+
}),
46+
)
47+
48+
testCase := func(t *testing.T, mode string) {
49+
resource.Test(t, resource.TestCase{
50+
PreCheck: func() { skipUnlessMode(t, mode) },
51+
Providers: testAccProviders,
52+
Steps: []resource.TestStep{
53+
{
54+
Config: config,
55+
Check: check,
56+
},
57+
},
58+
})
59+
}
60+
61+
t.Run("with an anonymous account", func(t *testing.T) {
62+
t.Skip("anonymous account not supported for this operation")
63+
})
64+
65+
t.Run("with an individual account", func(t *testing.T) {
66+
t.Skip("individual account not supported for this operation")
67+
})
68+
69+
t.Run("with an organization account", func(t *testing.T) {
70+
testCase(t, organization)
71+
})
72+
})
73+
74+
t.Run("creates custom property of type multi_select without error", func(t *testing.T) {
75+
76+
config := fmt.Sprintf(`
77+
resource "github_repository" "test" {
78+
name = "tf-acc-test-%s"
79+
auto_init = true
80+
}
81+
resource "github_repository_custom_property" "test" {
82+
repository = github_repository.test.name
83+
property_name = "%s"
84+
property_type = "multi_select"
85+
property_value = ["option1", "option2"]
86+
}
87+
data "github_repository_custom_properties" "test" {
88+
repository = github_repository_custom_property.test.repository
89+
}
90+
`, randomID, multiSelectPropertyName)
91+
92+
check := resource.ComposeTestCheckFunc(
93+
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
94+
"property.*", map[string]string{
95+
"property_name": multiSelectPropertyName,
96+
"property_value.#": "2",
97+
"property_value.0": "option1",
98+
"property_value.1": "option2",
99+
}),
100+
)
101+
102+
testCase := func(t *testing.T, mode string) {
103+
resource.Test(t, resource.TestCase{
104+
PreCheck: func() { skipUnlessMode(t, mode) },
105+
Providers: testAccProviders,
106+
Steps: []resource.TestStep{
107+
{
108+
Config: config,
109+
Check: check,
110+
},
111+
},
112+
})
113+
}
114+
115+
t.Run("with an anonymous account", func(t *testing.T) {
116+
t.Skip("anonymous account not supported for this operation")
117+
})
118+
119+
t.Run("with an individual account", func(t *testing.T) {
120+
t.Skip("individual account not supported for this operation")
121+
})
122+
123+
t.Run("with an organization account", func(t *testing.T) {
124+
testCase(t, organization)
125+
})
126+
})
127+
128+
t.Run("creates custom property of type true_false without error", func(t *testing.T) {
129+
130+
config := fmt.Sprintf(`
131+
resource "github_repository" "test" {
132+
name = "tf-acc-test-%s"
133+
auto_init = true
134+
}
135+
resource "github_repository_custom_property" "test" {
136+
repository = github_repository.test.name
137+
property_name = "%s"
138+
property_type = "true_false"
139+
property_value = ["true"]
140+
}
141+
data "github_repository_custom_properties" "test" {
142+
repository = github_repository_custom_property.test.repository
143+
}
144+
`, randomID, trueFlasePropertyName)
145+
146+
check := resource.ComposeTestCheckFunc(
147+
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
148+
"property.*", map[string]string{
149+
"property_name": trueFlasePropertyName,
150+
"property_value.#": "1",
151+
"property_value.0": "true",
152+
}),
153+
)
154+
155+
testCase := func(t *testing.T, mode string) {
156+
resource.Test(t, resource.TestCase{
157+
PreCheck: func() { skipUnlessMode(t, mode) },
158+
Providers: testAccProviders,
159+
Steps: []resource.TestStep{
160+
{
161+
Config: config,
162+
Check: check,
163+
},
164+
},
165+
})
166+
}
167+
168+
t.Run("with an anonymous account", func(t *testing.T) {
169+
t.Skip("anonymous account not supported for this operation")
170+
})
171+
172+
t.Run("with an individual account", func(t *testing.T) {
173+
t.Skip("individual account not supported for this operation")
174+
})
175+
176+
t.Run("with an organization account", func(t *testing.T) {
177+
testCase(t, organization)
178+
})
179+
})
180+
181+
t.Run("creates custom property of type string without error", func(t *testing.T) {
182+
183+
config := fmt.Sprintf(`
184+
resource "github_repository" "test" {
185+
name = "tf-acc-test-%s"
186+
auto_init = true
187+
}
188+
resource "github_repository_custom_property" "test" {
189+
repository = github_repository.test.name
190+
property_name = "%s"
191+
property_type = "string"
192+
property_value = ["text"]
193+
}
194+
data "github_repository_custom_properties" "test" {
195+
repository = github_repository_custom_property.test.repository
196+
}
197+
`, randomID, stringPropertyName)
198+
199+
check := resource.ComposeTestCheckFunc(
200+
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
201+
"property.*", map[string]string{
202+
"property_name": stringPropertyName,
203+
"property_value.#": "1",
204+
"property_value.0": "text",
205+
}),
206+
)
207+
208+
testCase := func(t *testing.T, mode string) {
209+
resource.Test(t, resource.TestCase{
210+
PreCheck: func() { skipUnlessMode(t, mode) },
211+
Providers: testAccProviders,
212+
Steps: []resource.TestStep{
213+
{
214+
Config: config,
215+
Check: check,
216+
},
217+
},
218+
})
219+
}
220+
221+
t.Run("with an anonymous account", func(t *testing.T) {
222+
t.Skip("anonymous account not supported for this operation")
223+
})
224+
225+
t.Run("with an individual account", func(t *testing.T) {
226+
t.Skip("individual account not supported for this operation")
227+
})
228+
229+
t.Run("with an organization account", func(t *testing.T) {
230+
testCase(t, organization)
231+
})
232+
})
233+
}

github/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ func Provider() *schema.Provider {
172172
"github_repository_dependabot_security_updates": resourceGithubRepositoryDependabotSecurityUpdates(),
173173
"github_repository_collaborator": resourceGithubRepositoryCollaborator(),
174174
"github_repository_collaborators": resourceGithubRepositoryCollaborators(),
175+
"github_repository_custom_property": resourceGithubRepositoryCustomProperty(),
175176
"github_repository_deploy_key": resourceGithubRepositoryDeployKey(),
176177
"github_repository_deployment_branch_policy": resourceGithubRepositoryDeploymentBranchPolicy(),
177178
"github_repository_environment": resourceGithubRepositoryEnvironment(),
@@ -241,6 +242,7 @@ func Provider() *schema.Provider {
241242
"github_repository": dataSourceGithubRepository(),
242243
"github_repository_autolink_references": dataSourceGithubRepositoryAutolinkReferences(),
243244
"github_repository_branches": dataSourceGithubRepositoryBranches(),
245+
"github_repository_custom_properties": dataSourceGithubRepositoryCustomProperties(),
244246
"github_repository_environments": dataSourceGithubRepositoryEnvironments(),
245247
"github_repository_deploy_keys": dataSourceGithubRepositoryDeployKeys(),
246248
"github_repository_deployment_branch_policies": dataSourceGithubRepositoryDeploymentBranchPolicies(),

0 commit comments

Comments
 (0)