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

+1
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

+1
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,
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+
}
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

+2
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.
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

+3
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)