Skip to content

Commit

Permalink
feat: support repository level custom_property resource and custom_pr…
Browse files Browse the repository at this point in the history
…operties 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]>
  • Loading branch information
3 people authored Jan 17, 2025
1 parent 0419c6b commit 1ca7092
Show file tree
Hide file tree
Showing 7 changed files with 788 additions and 0 deletions.
103 changes: 103 additions & 0 deletions github/data_source_github_repository_custom_properties.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package github

import (
"context"
"fmt"

"github.com/google/go-github/v66/github"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceGithubRepositoryCustomProperties() *schema.Resource {
return &schema.Resource{
Read: dataSourceGithubOrgaRepositoryCustomProperties,

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
Description: "Name of the repository which the custom properties should be on.",
},
"property": {
Type: schema.TypeSet,
Computed: true,
Description: "List of custom properties",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"property_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the custom property.",
},
"property_value": {
Type: schema.TypeSet,
Computed: true,
Description: "Value of the custom property.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
},
},
}
}

func dataSourceGithubOrgaRepositoryCustomProperties(d *schema.ResourceData, meta interface{}) error {

client := meta.(*Owner).v3client
ctx := context.Background()

owner := meta.(*Owner).name

repoName := d.Get("repository").(string)

allCustomProperties, _, err := client.Repositories.GetAllCustomPropertyValues(ctx, owner, repoName)
if err != nil {
return err
}

results, err := flattenRepositoryCustomProperties(allCustomProperties)
if err != nil {
return err
}

d.SetId(buildTwoPartID(owner, repoName))
d.Set("repository", repoName)
d.Set("property", results)

return nil
}

func flattenRepositoryCustomProperties(customProperties []*github.CustomPropertyValue) ([]interface{}, error) {

results := make([]interface{}, 0)
for _, prop := range customProperties {
result := make(map[string]interface{})

result["property_name"] = prop.PropertyName

propertyValue, err := parseRepositoryCustomPropertyValueToStringSlice(prop)
if err != nil {
return nil, err
}

result["property_value"] = propertyValue

results = append(results, result)
}

return results, nil
}

func parseRepositoryCustomPropertyValueToStringSlice(prop *github.CustomPropertyValue) ([]string, error) {
switch value := prop.Value.(type) {
case string:
return []string{value}, nil
case []string:
return value, nil
default:
return nil, fmt.Errorf("custom property value couldn't be parsed as a string or a list of strings: %s", value)
}
}
233 changes: 233 additions & 0 deletions github/data_source_github_repository_custom_properties_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package github

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccGithubRepositoryCustomPropertiesDataSource(t *testing.T) {

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
singleSelectPropertyName := "single-select" // Needs to be a of type single_select, and have "option1" as an option
multiSelectPropertyName := "multi-select" // Needs to be a of type multi_select, and have "option1" and "option2" as an options
trueFlasePropertyName := "true-false" // Needs to be a of type true_false, and have "option1" as an option
stringPropertyName := "string" // Needs to be a of type string, and have "option1" as an option

randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

t.Run("creates custom property of type single_select without error", func(t *testing.T) {

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_custom_property" "test" {
repository = github_repository.test.name
property_name = "%s"
property_type = "single_select"
property_value = ["option1"]
}
data "github_repository_custom_properties" "test" {
repository = github_repository_custom_property.test.repository
}
`, randomID, singleSelectPropertyName)

check := resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
"property.*", map[string]string{
"property_name": singleSelectPropertyName,
"property_value.#": "1",
"property_value.0": "option1",
}),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})

t.Run("creates custom property of type multi_select without error", func(t *testing.T) {

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_custom_property" "test" {
repository = github_repository.test.name
property_name = "%s"
property_type = "multi_select"
property_value = ["option1", "option2"]
}
data "github_repository_custom_properties" "test" {
repository = github_repository_custom_property.test.repository
}
`, randomID, multiSelectPropertyName)

check := resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
"property.*", map[string]string{
"property_name": multiSelectPropertyName,
"property_value.#": "2",
"property_value.0": "option1",
"property_value.1": "option2",
}),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})

t.Run("creates custom property of type true_false without error", func(t *testing.T) {

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_custom_property" "test" {
repository = github_repository.test.name
property_name = "%s"
property_type = "true_false"
property_value = ["true"]
}
data "github_repository_custom_properties" "test" {
repository = github_repository_custom_property.test.repository
}
`, randomID, trueFlasePropertyName)

check := resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
"property.*", map[string]string{
"property_name": trueFlasePropertyName,
"property_value.#": "1",
"property_value.0": "true",
}),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})

t.Run("creates custom property of type string without error", func(t *testing.T) {

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_custom_property" "test" {
repository = github_repository.test.name
property_name = "%s"
property_type = "string"
property_value = ["text"]
}
data "github_repository_custom_properties" "test" {
repository = github_repository_custom_property.test.repository
}
`, randomID, stringPropertyName)

check := resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs("data.github_repository_custom_properties.test",
"property.*", map[string]string{
"property_name": stringPropertyName,
"property_value.#": "1",
"property_value.0": "text",
}),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})
}
2 changes: 2 additions & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func Provider() *schema.Provider {
"github_repository_dependabot_security_updates": resourceGithubRepositoryDependabotSecurityUpdates(),
"github_repository_collaborator": resourceGithubRepositoryCollaborator(),
"github_repository_collaborators": resourceGithubRepositoryCollaborators(),
"github_repository_custom_property": resourceGithubRepositoryCustomProperty(),
"github_repository_deploy_key": resourceGithubRepositoryDeployKey(),
"github_repository_deployment_branch_policy": resourceGithubRepositoryDeploymentBranchPolicy(),
"github_repository_environment": resourceGithubRepositoryEnvironment(),
Expand Down Expand Up @@ -241,6 +242,7 @@ func Provider() *schema.Provider {
"github_repository": dataSourceGithubRepository(),
"github_repository_autolink_references": dataSourceGithubRepositoryAutolinkReferences(),
"github_repository_branches": dataSourceGithubRepositoryBranches(),
"github_repository_custom_properties": dataSourceGithubRepositoryCustomProperties(),
"github_repository_environments": dataSourceGithubRepositoryEnvironments(),
"github_repository_deploy_keys": dataSourceGithubRepositoryDeployKeys(),
"github_repository_deployment_branch_policies": dataSourceGithubRepositoryDeploymentBranchPolicies(),
Expand Down
Loading

0 comments on commit 1ca7092

Please sign in to comment.