Skip to content

Commit

Permalink
[FEAT]: Add github_repository_topics resource (integrations#1846)
Browse files Browse the repository at this point in the history
* only set topics if explicitly set in github_repository resource

* Revert "only set topics if explicitly set in github_repository resource"

This reverts commit 70977ca.

* add repository_topics resource

* change repository resource to compute topics if not set

* add validation function to the repo name

* import needs to set the repository argument

* add tests

* add docs for repository_topics resource

* formatting

* make the usage of github_repository_topics clearer

* add github_repository_topic to gtihub.erb

* add link to issue showing intended usage of repository_topics resource2

* Update website/docs/r/repository.html.markdown

---------

Co-authored-by: Keegan Campbell <[email protected]>
  • Loading branch information
felixlut and kfcampbell authored Sep 8, 2023
1 parent a7237af commit 5396104
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 0 deletions.
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ func Provider() terraform.ResourceProvider {
"github_repository_pull_request": resourceGithubRepositoryPullRequest(),
"github_repository_ruleset": resourceGithubRepositoryRuleset(),
"github_repository_tag_protection": resourceGithubRepositoryTagProtection(),
"github_repository_topics": resourceGithubRepositoryTopics(),
"github_repository_webhook": resourceGithubRepositoryWebhook(),
"github_team": resourceGithubTeam(),
"github_team_members": resourceGithubTeamMembers(),
Expand Down
1 change: 1 addition & 0 deletions github/resource_github_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ func resourceGithubRepository() *schema.Resource {
"topics": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Description: "The list of topics of the repository.",
Elem: &schema.Schema{
Type: schema.TypeString,
Expand Down
91 changes: 91 additions & 0 deletions github/resource_github_repository_topics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package github

import (
"context"
"regexp"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceGithubRepositoryTopics() *schema.Resource {
return &schema.Resource{
Create: resourceGithubRepositoryTopicsCreateOrUpdate,
Read: resourceGithubRepositoryTopicsRead,
Update: resourceGithubRepositoryTopicsCreateOrUpdate,
Delete: resourceGithubRepositoryTopicsDelete,
Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.Set("repository", d.Id())
return []*schema.ResourceData{d}, nil
},
},
Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[-a-zA-Z0-9_.]{1,100}$`), "must include only alphanumeric characters, underscores or hyphens and consist of 100 characters or less"),
Description: "The name of the repository. The name is not case sensitive.",
},
"topics": {
Type: schema.TypeSet,
Required: true,
Description: "An array of topics to add to the repository. Pass one or more topics to replace the set of existing topics. Send an empty array ([]) to clear all topics from the repository. Note: Topic names cannot contain uppercase letters.",
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,49}$`), "must include only lowercase alphanumeric characters or hyphens and cannot start with a hyphen and consist of 50 characters or less"),
},
}},
}

}

func resourceGithubRepositoryTopicsCreateOrUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.Background()

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
topics := expandStringList(d.Get("topics").(*schema.Set).List())

if len(topics) > 0 {
_, _, err := client.Repositories.ReplaceAllTopics(ctx, owner, repoName, topics)
if err != nil {
return err
}
}

d.SetId(repoName)
return resourceGithubRepositoryTopicsRead(d, meta)
}

func resourceGithubRepositoryTopicsRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.WithValue(context.Background(), ctxId, d.Id())

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)

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

d.Set("topics", flattenStringList(topics))
return nil
}

func resourceGithubRepositoryTopicsDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.WithValue(context.Background(), ctxId, d.Id())

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)

_, _, err := client.Repositories.ReplaceAllTopics(ctx, owner, repoName, []string{})
if err != nil {
return err
}

return nil
}
129 changes: 129 additions & 0 deletions github/resource_github_repository_topics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package github

import (
"fmt"
"testing"

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

func TestAccGithubRepositoryTopics(t *testing.T) {
t.Run("create repository topics and import them", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_topics" "test" {
repository = github_repository.test.name
topics = ["test", "test-2"]
}
`, randomID)

const resourceName = "github_repository_topics.test"

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "topics.#", "2"),
)

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,
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

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) {
testCase(t, individual)
})

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

t.Run("create repository topics and update them", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

configBefore := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_topics" "test" {
repository = github_repository.test.name
topics = ["test", "test-2"]
}
`, randomID)

configAfter := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_topics" "test" {
repository = github_repository.test.name
topics = ["test", "test-2", "extra-topic"]
}
`, randomID)

const resourceName = "github_repository_topics.test"

checkBefore := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "topics.#", "2"),
)
checkAfter := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "topics.#", "3"),
)

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

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) {
testCase(t, individual)
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})
}
2 changes: 2 additions & 0 deletions website/docs/r/repository.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ initial repository creation and create the target branch inside of the repositor

* `topics` - (Optional) The list of topics of the repository.

~> Note: This attribute is not compatible with the `github_repository_topics` resource. Use one of them. `github_repository_topics` is only meant to be used if the repository itself is not handled via terraform, for example if it's only read as a datasource (see [issue #1845](https://github.com/integrations/terraform-provider-github/issues/1845)).

* `template` - (Optional) Use a template repository to create this resource. See [Template Repositories](#template-repositories) below for details.

* `vulnerability_alerts` (Optional) - Set to `true` to enable security alerts for vulnerable dependencies. Enabling requires alerts to be enabled on the owner level. (Note for importing: GitHub enables the alerts on public repos but disables them on private repos by default.) See [GitHub Documentation](https://help.github.com/en/github/managing-security-vulnerabilities/about-security-alerts-for-vulnerable-dependencies) for details. Note that vulnerability alerts have not been successfully tested on any GitHub Enterprise instance and may be unavailable in those settings.
Expand Down
42 changes: 42 additions & 0 deletions website/docs/r/repository_topics.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
layout: "github"
page_title: "GitHub: github_repository_topics"
description: |-
Creates and manages the topics on a repository
---

# github_repository_topics

This resource allows you to create and manage topics for repositories within your GitHub organization or personal account.

~> Note: This resource is not compatible with the `topic` attribute of the `github_repository` Use either ``github_repository_topics``
or ``topic`` in ``github_repository``. `github_repository_topics` is only meant to be used if the repository itself is not handled via terraform, for example if it's only read as a datasource (see [issue #1845](https://github.com/integrations/terraform-provider-github/issues/1845)).

## Example Usage

```hcl
data "github_repository" "test" {
name = "test"
}
resource "github_repository_topics" "test" {
repository = github_repository.test.name
topics = ["topic-1", "topic-2"]
}
```

## Argument Reference

The following arguments are supported:

* `repository` - (Required) The repository name.

* `topics` - (Required) A list of topics to add to the repository.

## Import

Repository topics can be imported using the `name` of the repository.

```
$ terraform import github_repository_topics.terraform terraform
```
3 changes: 3 additions & 0 deletions website/github.erb
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,9 @@
<li>
<a href="/docs/providers/github/r/repository_tag_protection.html">github_repository_tag_protection</a>
</li>
<li>
<a href="/docs/providers/github/r/repository_topics.html">github_repository_topics</a>
</li>
<li>
<a href="/docs/providers/github/r/repository_webhook.html">github_repository_webhook</a>
</li>
Expand Down

0 comments on commit 5396104

Please sign in to comment.