Skip to content

Commit 5396104

Browse files
felixlutkfcampbell
andauthored
[FEAT]: Add github_repository_topics resource (#1846)
* 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]>
1 parent a7237af commit 5396104

7 files changed

+269
-0
lines changed

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ func Provider() terraform.ResourceProvider {
150150
"github_repository_pull_request": resourceGithubRepositoryPullRequest(),
151151
"github_repository_ruleset": resourceGithubRepositoryRuleset(),
152152
"github_repository_tag_protection": resourceGithubRepositoryTagProtection(),
153+
"github_repository_topics": resourceGithubRepositoryTopics(),
153154
"github_repository_webhook": resourceGithubRepositoryWebhook(),
154155
"github_team": resourceGithubTeam(),
155156
"github_team_members": resourceGithubTeamMembers(),

github/resource_github_repository.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ func resourceGithubRepository() *schema.Resource {
302302
"topics": {
303303
Type: schema.TypeSet,
304304
Optional: true,
305+
Computed: true,
305306
Description: "The list of topics of the repository.",
306307
Elem: &schema.Schema{
307308
Type: schema.TypeString,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"regexp"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
8+
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
9+
)
10+
11+
func resourceGithubRepositoryTopics() *schema.Resource {
12+
return &schema.Resource{
13+
Create: resourceGithubRepositoryTopicsCreateOrUpdate,
14+
Read: resourceGithubRepositoryTopicsRead,
15+
Update: resourceGithubRepositoryTopicsCreateOrUpdate,
16+
Delete: resourceGithubRepositoryTopicsDelete,
17+
Importer: &schema.ResourceImporter{
18+
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
19+
d.Set("repository", d.Id())
20+
return []*schema.ResourceData{d}, nil
21+
},
22+
},
23+
Schema: map[string]*schema.Schema{
24+
"repository": {
25+
Type: schema.TypeString,
26+
Required: true,
27+
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"),
28+
Description: "The name of the repository. The name is not case sensitive.",
29+
},
30+
"topics": {
31+
Type: schema.TypeSet,
32+
Required: true,
33+
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.",
34+
Elem: &schema.Schema{
35+
Type: schema.TypeString,
36+
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"),
37+
},
38+
}},
39+
}
40+
41+
}
42+
43+
func resourceGithubRepositoryTopicsCreateOrUpdate(d *schema.ResourceData, meta interface{}) error {
44+
client := meta.(*Owner).v3client
45+
ctx := context.Background()
46+
47+
owner := meta.(*Owner).name
48+
repoName := d.Get("repository").(string)
49+
topics := expandStringList(d.Get("topics").(*schema.Set).List())
50+
51+
if len(topics) > 0 {
52+
_, _, err := client.Repositories.ReplaceAllTopics(ctx, owner, repoName, topics)
53+
if err != nil {
54+
return err
55+
}
56+
}
57+
58+
d.SetId(repoName)
59+
return resourceGithubRepositoryTopicsRead(d, meta)
60+
}
61+
62+
func resourceGithubRepositoryTopicsRead(d *schema.ResourceData, meta interface{}) error {
63+
client := meta.(*Owner).v3client
64+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
65+
66+
owner := meta.(*Owner).name
67+
repoName := d.Get("repository").(string)
68+
69+
topics, _, err := client.Repositories.ListAllTopics(ctx, owner, repoName)
70+
if err != nil {
71+
return err
72+
}
73+
74+
d.Set("topics", flattenStringList(topics))
75+
return nil
76+
}
77+
78+
func resourceGithubRepositoryTopicsDelete(d *schema.ResourceData, meta interface{}) error {
79+
client := meta.(*Owner).v3client
80+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
81+
82+
owner := meta.(*Owner).name
83+
repoName := d.Get("repository").(string)
84+
85+
_, _, err := client.Repositories.ReplaceAllTopics(ctx, owner, repoName, []string{})
86+
if err != nil {
87+
return err
88+
}
89+
90+
return nil
91+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package github
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
8+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
9+
)
10+
11+
func TestAccGithubRepositoryTopics(t *testing.T) {
12+
t.Run("create repository topics and import them", func(t *testing.T) {
13+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
14+
15+
config := fmt.Sprintf(`
16+
resource "github_repository" "test" {
17+
name = "tf-acc-test-%s"
18+
auto_init = true
19+
}
20+
21+
resource "github_repository_topics" "test" {
22+
repository = github_repository.test.name
23+
topics = ["test", "test-2"]
24+
}
25+
`, randomID)
26+
27+
const resourceName = "github_repository_topics.test"
28+
29+
check := resource.ComposeTestCheckFunc(
30+
resource.TestCheckResourceAttr(resourceName, "topics.#", "2"),
31+
)
32+
33+
testCase := func(t *testing.T, mode string) {
34+
resource.Test(t, resource.TestCase{
35+
PreCheck: func() { skipUnlessMode(t, mode) },
36+
Providers: testAccProviders,
37+
Steps: []resource.TestStep{
38+
{
39+
Config: config,
40+
Check: check,
41+
},
42+
{
43+
ResourceName: resourceName,
44+
ImportState: true,
45+
ImportStateVerify: true,
46+
},
47+
},
48+
})
49+
}
50+
51+
t.Run("with an anonymous account", func(t *testing.T) {
52+
t.Skip("anonymous account not supported for this operation")
53+
})
54+
55+
t.Run("with an individual account", func(t *testing.T) {
56+
testCase(t, individual)
57+
})
58+
59+
t.Run("with an organization account", func(t *testing.T) {
60+
testCase(t, organization)
61+
})
62+
})
63+
64+
t.Run("create repository topics and update them", func(t *testing.T) {
65+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
66+
67+
configBefore := fmt.Sprintf(`
68+
resource "github_repository" "test" {
69+
name = "tf-acc-test-%s"
70+
auto_init = true
71+
}
72+
73+
resource "github_repository_topics" "test" {
74+
repository = github_repository.test.name
75+
topics = ["test", "test-2"]
76+
}
77+
`, randomID)
78+
79+
configAfter := fmt.Sprintf(`
80+
resource "github_repository" "test" {
81+
name = "tf-acc-test-%s"
82+
auto_init = true
83+
}
84+
85+
resource "github_repository_topics" "test" {
86+
repository = github_repository.test.name
87+
topics = ["test", "test-2", "extra-topic"]
88+
}
89+
`, randomID)
90+
91+
const resourceName = "github_repository_topics.test"
92+
93+
checkBefore := resource.ComposeTestCheckFunc(
94+
resource.TestCheckResourceAttr(resourceName, "topics.#", "2"),
95+
)
96+
checkAfter := resource.ComposeTestCheckFunc(
97+
resource.TestCheckResourceAttr(resourceName, "topics.#", "3"),
98+
)
99+
100+
testCase := func(t *testing.T, mode string) {
101+
resource.Test(t, resource.TestCase{
102+
PreCheck: func() { skipUnlessMode(t, mode) },
103+
Providers: testAccProviders,
104+
Steps: []resource.TestStep{
105+
{
106+
Config: configBefore,
107+
Check: checkBefore,
108+
},
109+
{
110+
Config: configAfter,
111+
Check: checkAfter,
112+
},
113+
},
114+
})
115+
}
116+
117+
t.Run("with an anonymous account", func(t *testing.T) {
118+
t.Skip("anonymous account not supported for this operation")
119+
})
120+
121+
t.Run("with an individual account", func(t *testing.T) {
122+
testCase(t, individual)
123+
})
124+
125+
t.Run("with an organization account", func(t *testing.T) {
126+
testCase(t, organization)
127+
})
128+
})
129+
}

website/docs/r/repository.html.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ initial repository creation and create the target branch inside of the repositor
116116

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

119+
~> 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)).
120+
119121
* `template` - (Optional) Use a template repository to create this resource. See [Template Repositories](#template-repositories) below for details.
120122

121123
* `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.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
layout: "github"
3+
page_title: "GitHub: github_repository_topics"
4+
description: |-
5+
Creates and manages the topics on a repository
6+
---
7+
8+
# github_repository_topics
9+
10+
This resource allows you to create and manage topics for repositories within your GitHub organization or personal account.
11+
12+
~> Note: This resource is not compatible with the `topic` attribute of the `github_repository` Use either ``github_repository_topics``
13+
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)).
14+
15+
## Example Usage
16+
17+
```hcl
18+
data "github_repository" "test" {
19+
name = "test"
20+
}
21+
22+
resource "github_repository_topics" "test" {
23+
repository = github_repository.test.name
24+
topics = ["topic-1", "topic-2"]
25+
}
26+
```
27+
28+
## Argument Reference
29+
30+
The following arguments are supported:
31+
32+
* `repository` - (Required) The repository name.
33+
34+
* `topics` - (Required) A list of topics to add to the repository.
35+
36+
## Import
37+
38+
Repository topics can be imported using the `name` of the repository.
39+
40+
```
41+
$ terraform import github_repository_topics.terraform terraform
42+
```

website/github.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@
343343
<li>
344344
<a href="/docs/providers/github/r/repository_tag_protection.html">github_repository_tag_protection</a>
345345
</li>
346+
<li>
347+
<a href="/docs/providers/github/r/repository_topics.html">github_repository_topics</a>
348+
</li>
346349
<li>
347350
<a href="/docs/providers/github/r/repository_webhook.html">github_repository_webhook</a>
348351
</li>

0 commit comments

Comments
 (0)