diff --git a/github/provider.go b/github/provider.go index 8f44c95098..296f1c07ba 100644 --- a/github/provider.go +++ b/github/provider.go @@ -133,6 +133,7 @@ func Provider() *schema.Provider { "github_actions_organization_secret": resourceGithubActionsOrganizationSecret(), "github_actions_organization_variable": resourceGithubActionsOrganizationVariable(), "github_actions_organization_secret_repositories": resourceGithubActionsOrganizationSecretRepositories(), + "github_actions_organization_secret_repository": resourceGithubActionsOrganizationSecretRepository(), "github_actions_repository_access_level": resourceGithubActionsRepositoryAccessLevel(), "github_actions_repository_oidc_subject_claim_customization_template": resourceGithubActionsRepositoryOIDCSubjectClaimCustomizationTemplate(), "github_actions_repository_permissions": resourceGithubActionsRepositoryPermissions(), diff --git a/github/resource_github_actions_organization_secret_repository.go b/github/resource_github_actions_organization_secret_repository.go new file mode 100644 index 0000000000..6cdd00e305 --- /dev/null +++ b/github/resource_github_actions_organization_secret_repository.go @@ -0,0 +1,144 @@ +package github + +import ( + "context" + "log" + "strconv" + + "github.com/google/go-github/v66/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceGithubActionsOrganizationSecretRepository() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubActionsOrganizationSecretRepositoryCreate, + Read: resourceGithubActionsOrganizationSecretRepositoryRead, + Delete: resourceGithubActionsOrganizationSecretRepositoryDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "secret_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the existing secret.", + ValidateDiagFunc: validateSecretNameFunc, + }, + "repository_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "The repository ID that can access the organization secret.", + }, + }, + } +} + +func resourceGithubActionsOrganizationSecretRepositoryCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + ctx := context.Background() + + err := checkOrganization(meta) + if err != nil { + return err + } + + repositoryID := d.Get("repository_id").(int) + secretName := d.Get("secret_name").(string) + + repoIDInt64 := int64(repositoryID) + repository := &github.Repository{ + ID: &repoIDInt64, + } + + _, err = client.Actions.AddSelectedRepoToOrgSecret(ctx, owner, secretName, repository) + + if err != nil { + return err + } + + d.SetId(buildTwoPartID(secretName, strconv.Itoa(repositoryID))) + return resourceGithubActionsOrganizationSecretRepositoryRead(d, meta) +} + +func resourceGithubActionsOrganizationSecretRepositoryRead(d *schema.ResourceData, meta interface{}) error { + owner := meta.(*Owner).name + + err := checkOrganization(meta) + if err != nil { + return err + } + client := meta.(*Owner).v3client + + secretName, repositoryIDString, err := parseTwoPartID(d.Id(), "secret_name", "repository_id") + if err != nil { + return err + } + + repositoryID, err := strconv.ParseInt(repositoryIDString, 10, 64) + if err != nil { + return unconvertibleIdErr(repositoryIDString, err) + } + + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + opt := &github.ListOptions{ + PerPage: maxPerPage, + } + for { + repos, resp, err := client.Actions.ListSelectedReposForOrgSecret(ctx, owner, secretName, opt) + if err != nil { + return err + } + + for _, repo := range repos.Repositories { + if repo.GetID() == repositoryID { + if err = d.Set("secret_name", secretName); err != nil { + return err + } + if err = d.Set("repository_id", repositoryID); err != nil { + return err + } + return nil + } + } + + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + + log.Printf("[INFO] Removing secret repository association %s from state because it no longer exists in GitHub", + d.Id()) + d.SetId("") + + return nil +} + +func resourceGithubActionsOrganizationSecretRepositoryDelete(d *schema.ResourceData, meta interface{}) error { + err := checkOrganization(meta) + if err != nil { + return err + } + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + secretName := d.Get("secret_name").(string) + repositoryID := d.Get("repository_id").(int) + + repoIDInt64 := int64(repositoryID) + repository := &github.Repository{ + ID: &repoIDInt64, + } + _, err = client.Actions.RemoveSelectedRepoFromOrgSecret(ctx, owner, secretName, repository) + if err != nil { + return err + } + + return nil +} diff --git a/github/resource_github_actions_organization_secret_repository_test.go b/github/resource_github_actions_organization_secret_repository_test.go new file mode 100644 index 0000000000..5d587a4234 --- /dev/null +++ b/github/resource_github_actions_organization_secret_repository_test.go @@ -0,0 +1,70 @@ +package github + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubActionsOrganizationSecretRepository(t *testing.T) { + + const ORG_SECRET_NAME = "ORG_SECRET_NAME" + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + secret_name, exists := os.LookupEnv(ORG_SECRET_NAME) + + t.Run("set repository allowlist for a organization secret", func(t *testing.T) { + if !exists { + t.Skipf("%s environment variable is missing", ORG_SECRET_NAME) + } + + config := fmt.Sprintf(` + resource "github_repository" "test_repo_1" { + name = "tf-acc-test-%s-1" + visibility = "internal" + vulnerability_alerts = "true" + } + + resource "github_actions_organization_secret_repository" "org_secret_repo" { + secret_name = "%s" + repository_id = github_repository.test_repo_1.repo_id + } + `, randomID, secret_name) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "github_actions_organization_secret_repository.org_secret_repo", "secret_name", + ), + resource.TestCheckResourceAttr( + "github_actions_organization_secret_repository.org_secret_repo", "repository_id.#", "1", + ), + ) + + 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) + }) + }) +} diff --git a/website/docs/r/actions_organization_secret_repository.html.markdown b/website/docs/r/actions_organization_secret_repository.html.markdown new file mode 100644 index 0000000000..0fd9f54711 --- /dev/null +++ b/website/docs/r/actions_organization_secret_repository.html.markdown @@ -0,0 +1,41 @@ +--- +layout: "github" +page_title: "GitHub: github_actions_organization_secret_repository" +description: |- + Adds/remove a repository to an organization secret when the visibility for repository access is set to selected. +--- + +# github_actions_organization_secret_repository + +This resource help you to allow/unallow a repository to use an existing GitHub Actions secrets within your GitHub organization. +You must have write access to an organization secret to use this resource. + +This resource is only applicable when `visibility` of the existing organization secret has been set to `selected`. + +## Example Usage + +```hcl +data "github_repository" "repo" { + full_name = "my-org/repo" +} + +resource "github_actions_organization_secret_repository" "org_secret_repos" { + secret_name = "EXAMPLE_SECRET_NAME" + repository_id = github_repository.repo.repo_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `secret_name` - (Required) Name of the existing secret +* `repository_id` - (Required) Repository id that can access the organization secret. + +## Import + +This resource can be imported using an ID made up of the secret name: + +``` +$ terraform import github_actions_organization_secret_repository.test_secret_repos test_secret_name:repo_id +```