Skip to content

Commit 8703f48

Browse files
author
Yohan Lascombe
committed
feat: Add github_actions_organization_secret_repository resource
Resolve Issue #2759
1 parent 9fceeda commit 8703f48

File tree

4 files changed

+256
-0
lines changed

4 files changed

+256
-0
lines changed

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ func Provider() *schema.Provider {
133133
"github_actions_organization_secret": resourceGithubActionsOrganizationSecret(),
134134
"github_actions_organization_variable": resourceGithubActionsOrganizationVariable(),
135135
"github_actions_organization_secret_repositories": resourceGithubActionsOrganizationSecretRepositories(),
136+
"github_actions_organization_secret_repository": resourceGithubActionsOrganizationSecretRepository(),
136137
"github_actions_repository_access_level": resourceGithubActionsRepositoryAccessLevel(),
137138
"github_actions_repository_oidc_subject_claim_customization_template": resourceGithubActionsRepositoryOIDCSubjectClaimCustomizationTemplate(),
138139
"github_actions_repository_permissions": resourceGithubActionsRepositoryPermissions(),
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"log"
6+
"strconv"
7+
8+
"github.com/google/go-github/v66/github"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
)
11+
12+
func resourceGithubActionsOrganizationSecretRepository() *schema.Resource {
13+
return &schema.Resource{
14+
Create: resourceGithubActionsOrganizationSecretRepositoryCreate,
15+
Read: resourceGithubActionsOrganizationSecretRepositoryRead,
16+
Delete: resourceGithubActionsOrganizationSecretRepositoryDelete,
17+
Importer: &schema.ResourceImporter{
18+
StateContext: schema.ImportStatePassthroughContext,
19+
},
20+
21+
Schema: map[string]*schema.Schema{
22+
"secret_name": {
23+
Type: schema.TypeString,
24+
Required: true,
25+
ForceNew: true,
26+
Description: "Name of the existing secret.",
27+
ValidateDiagFunc: validateSecretNameFunc,
28+
},
29+
"repository_id": {
30+
Type: schema.TypeInt,
31+
Required: true,
32+
ForceNew: true,
33+
Description: "The repository ID that can access the organization secret.",
34+
},
35+
},
36+
}
37+
}
38+
39+
func resourceGithubActionsOrganizationSecretRepositoryCreate(d *schema.ResourceData, meta interface{}) error {
40+
client := meta.(*Owner).v3client
41+
owner := meta.(*Owner).name
42+
ctx := context.Background()
43+
44+
err := checkOrganization(meta)
45+
if err != nil {
46+
return err
47+
}
48+
49+
repositoryID := d.Get("repository_id").(int)
50+
secretName := d.Get("secret_name").(string)
51+
52+
repoIDInt64 := int64(repositoryID)
53+
repository := &github.Repository{
54+
ID: &repoIDInt64,
55+
}
56+
57+
_, err = client.Actions.AddSelectedRepoToOrgSecret(ctx, owner, secretName, repository)
58+
59+
if err != nil {
60+
return err
61+
}
62+
63+
d.SetId(buildTwoPartID(secretName, strconv.Itoa(repositoryID)))
64+
return resourceGithubActionsOrganizationSecretRepositoryRead(d, meta)
65+
}
66+
67+
func resourceGithubActionsOrganizationSecretRepositoryRead(d *schema.ResourceData, meta interface{}) error {
68+
owner := meta.(*Owner).name
69+
70+
err := checkOrganization(meta)
71+
if err != nil {
72+
return err
73+
}
74+
client := meta.(*Owner).v3client
75+
76+
secretName, repositoryIDString, err := parseTwoPartID(d.Id(), "secret_name", "repository_id")
77+
if err != nil {
78+
return err
79+
}
80+
81+
repositoryID, err := strconv.ParseInt(repositoryIDString, 10, 64)
82+
if err != nil {
83+
return unconvertibleIdErr(repositoryIDString, err)
84+
}
85+
86+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
87+
88+
opt := &github.ListOptions{
89+
PerPage: maxPerPage,
90+
}
91+
for {
92+
repos, resp, err := client.Actions.ListSelectedReposForOrgSecret(ctx, owner, secretName, opt)
93+
if err != nil {
94+
return err
95+
}
96+
97+
for _, repo := range repos.Repositories {
98+
if repo.GetID() == repositoryID {
99+
if err = d.Set("secret_name", secretName); err != nil {
100+
return err
101+
}
102+
if err = d.Set("repository_id", repositoryID); err != nil {
103+
return err
104+
}
105+
return nil
106+
}
107+
}
108+
109+
if resp.NextPage == 0 {
110+
break
111+
}
112+
opt.Page = resp.NextPage
113+
}
114+
115+
log.Printf("[INFO] Removing secret repository association %s from state because it no longer exists in GitHub",
116+
d.Id())
117+
d.SetId("")
118+
119+
return nil
120+
}
121+
122+
func resourceGithubActionsOrganizationSecretRepositoryDelete(d *schema.ResourceData, meta interface{}) error {
123+
err := checkOrganization(meta)
124+
if err != nil {
125+
return err
126+
}
127+
client := meta.(*Owner).v3client
128+
owner := meta.(*Owner).name
129+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
130+
131+
secretName := d.Get("secret_name").(string)
132+
repositoryID := d.Get("repository_id").(int)
133+
134+
repoIDInt64 := int64(repositoryID)
135+
repository := &github.Repository{
136+
ID: &repoIDInt64,
137+
}
138+
_, err = client.Actions.RemoveSelectedRepoFromOrgSecret(ctx, owner, secretName, repository)
139+
if err != nil {
140+
return err
141+
}
142+
143+
return nil
144+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package github
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10+
)
11+
12+
func TestAccGithubActionsOrganizationSecretRepository(t *testing.T) {
13+
14+
const ORG_SECRET_NAME = "ORG_SECRET_NAME"
15+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
16+
secret_name, exists := os.LookupEnv(ORG_SECRET_NAME)
17+
18+
t.Run("set repository allowlist for a organization secret", func(t *testing.T) {
19+
if !exists {
20+
t.Skipf("%s environment variable is missing", ORG_SECRET_NAME)
21+
}
22+
23+
config := fmt.Sprintf(`
24+
resource "github_repository" "test_repo_1" {
25+
name = "tf-acc-test-%s-1"
26+
visibility = "internal"
27+
vulnerability_alerts = "true"
28+
}
29+
30+
resource "github_actions_organization_secret_repository" "org_secret_repo" {
31+
secret_name = "%s"
32+
repository_id = github_repository.test_repo_1.repo_id
33+
}
34+
`, randomID, secret_name)
35+
36+
check := resource.ComposeTestCheckFunc(
37+
resource.TestCheckResourceAttrSet(
38+
"github_actions_organization_secret_repository.org_secret_repo", "secret_name",
39+
),
40+
resource.TestCheckResourceAttr(
41+
"github_actions_organization_secret_repository.org_secret_repo", "repository_id.#", "1",
42+
),
43+
)
44+
45+
testCase := func(t *testing.T, mode string) {
46+
resource.Test(t, resource.TestCase{
47+
PreCheck: func() { skipUnlessMode(t, mode) },
48+
Providers: testAccProviders,
49+
Steps: []resource.TestStep{
50+
{
51+
Config: config,
52+
Check: check,
53+
},
54+
},
55+
})
56+
}
57+
58+
t.Run("with an anonymous account", func(t *testing.T) {
59+
t.Skip("anonymous account not supported for this operation")
60+
})
61+
62+
t.Run("with an individual account", func(t *testing.T) {
63+
t.Skip("individual account not supported for this operation")
64+
})
65+
66+
t.Run("with an organization account", func(t *testing.T) {
67+
testCase(t, organization)
68+
})
69+
})
70+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
layout: "github"
3+
page_title: "GitHub: github_actions_organization_secret_repository"
4+
description: |-
5+
Adds/remove a repository to an organization secret when the visibility for repository access is set to selected.
6+
---
7+
8+
# github_actions_organization_secret_repository
9+
10+
This resource help you to allow/unallow a repository to use an existing GitHub Actions secrets within your GitHub organization.
11+
You must have write access to an organization secret to use this resource.
12+
13+
This resource is only applicable when `visibility` of the existing organization secret has been set to `selected`.
14+
15+
## Example Usage
16+
17+
```hcl
18+
data "github_repository" "repo" {
19+
full_name = "my-org/repo"
20+
}
21+
22+
resource "github_actions_organization_secret_repository" "org_secret_repos" {
23+
secret_name = "EXAMPLE_SECRET_NAME"
24+
repository_id = github_repository.repo.repo_id
25+
}
26+
```
27+
28+
## Argument Reference
29+
30+
The following arguments are supported:
31+
32+
* `secret_name` - (Required) Name of the existing secret
33+
* `repository_id` - (Required) Repository id that can access the organization secret.
34+
35+
## Import
36+
37+
This resource can be imported using an ID made up of the secret name:
38+
39+
```
40+
$ terraform import github_actions_organization_secret_repository.test_secret_repos test_secret_name:repo_id
41+
```

0 commit comments

Comments
 (0)