diff --git a/github/provider.go b/github/provider.go index 9eb9d0086c..b30078fe56 100644 --- a/github/provider.go +++ b/github/provider.go @@ -42,24 +42,25 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "github_branch_protection": resourceGithubBranchProtection(), - "github_issue_label": resourceGithubIssueLabel(), - "github_membership": resourceGithubMembership(), - "github_organization_block": resourceOrganizationBlock(), - "github_organization_project": resourceGithubOrganizationProject(), - "github_organization_webhook": resourceGithubOrganizationWebhook(), - "github_project_column": resourceGithubProjectColumn(), - "github_repository_collaborator": resourceGithubRepositoryCollaborator(), - "github_repository_deploy_key": resourceGithubRepositoryDeployKey(), - "github_repository_project": resourceGithubRepositoryProject(), - "github_repository_webhook": resourceGithubRepositoryWebhook(), - "github_repository": resourceGithubRepository(), - "github_team_membership": resourceGithubTeamMembership(), - "github_team_repository": resourceGithubTeamRepository(), - "github_team": resourceGithubTeam(), - "github_user_gpg_key": resourceGithubUserGpgKey(), - "github_user_invitation_accepter": resourceGithubUserInvitationAccepter(), - "github_user_ssh_key": resourceGithubUserSshKey(), + "github_branch_protection": resourceGithubBranchProtection(), + "github_issue_label": resourceGithubIssueLabel(), + "github_membership": resourceGithubMembership(), + "github_organization_block": resourceOrganizationBlock(), + "github_organization_project": resourceGithubOrganizationProject(), + "github_organization_webhook": resourceGithubOrganizationWebhook(), + "github_project_column": resourceGithubProjectColumn(), + "github_repository_collaborator": resourceGithubRepositoryCollaborator(), + "github_repository_deploy_key": resourceGithubRepositoryDeployKey(), + "github_repository_project": resourceGithubRepositoryProject(), + "github_repository_webhook": resourceGithubRepositoryWebhook(), + "github_repository_prereceive_hook": resourceGithubRepositoryPreReceiveHook(), + "github_repository": resourceGithubRepository(), + "github_team_membership": resourceGithubTeamMembership(), + "github_team_repository": resourceGithubTeamRepository(), + "github_team": resourceGithubTeam(), + "github_user_gpg_key": resourceGithubUserGpgKey(), + "github_user_invitation_accepter": resourceGithubUserInvitationAccepter(), + "github_user_ssh_key": resourceGithubUserSshKey(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/github/resource_github_repository_prereceive_hook.go b/github/resource_github_repository_prereceive_hook.go new file mode 100644 index 0000000000..19269d4a63 --- /dev/null +++ b/github/resource_github_repository_prereceive_hook.go @@ -0,0 +1,165 @@ +package github + +import ( + "context" + "fmt" + "strconv" + + "github.com/google/go-github/v25/github" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceGithubRepositoryPreReceiveHook() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubRepositoryPreReceiveHookCreateUpdate, + Read: resourceGithubRepositoryPreReceiveHookRead, + Update: resourceGithubRepositoryPreReceiveHookCreateUpdate, + Delete: resourceGithubRepositoryPreReceiveHookDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "repository": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "enforcement": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"enabled", "disabled", "testing"}, false), + }, + "config_url": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func fetchGitHubRepositoryPreReceiveHookByName(meta interface{}, repoName, hookName string) (*github.PreReceiveHook, error) { + ctx := context.Background() + client := meta.(*Organization).client + orgName := meta.(*Organization).name + + opt := &github.ListOptions{ + PerPage: 100, + } + + var hook *github.PreReceiveHook + + for { + hooks, resp, err := client.Repositories.ListPreReceiveHooks(ctx, orgName, repoName, opt) + if err != nil { + return nil, err + } + + for _, h := range hooks { + n := *h.Name + if n == hookName { + hook = h + break + } + } + + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + + if *hook.ID <= 0 { + return nil, fmt.Errorf("No pre-receive hook with name %s found on %s/%s", hookName, orgName, repoName) + } + + return hook, nil +} + +func resourceGithubRepositoryPreReceiveHookCreateUpdate(d *schema.ResourceData, meta interface{}) error { + err := checkOrganization(meta) + if err != nil { + return err + } + + repoName := d.Get("repository").(string) + hookName := d.Get("name").(string) + + hook, err := fetchGitHubRepositoryPreReceiveHookByName(meta, repoName, hookName) + if err != nil { + return err + } + + enforcement := d.Get("enforcement").(string) + hook.Enforcement = &enforcement + + if v, ok := d.GetOk("config_url"); ok { + configURL := v.(string) + hook.ConfigURL = &configURL + } + + ctx := context.Background() + client := meta.(*Organization).client + orgName := meta.(*Organization).name + _, _, err = client.Repositories.UpdatePreReceiveHook(ctx, orgName, repoName, *hook.ID, hook) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("%s/%s/%s", orgName, repoName, strconv.FormatInt(*hook.ID, 10))) + + return resourceGithubRepositoryPreReceiveHookRead(d, meta) +} + +func resourceGithubRepositoryPreReceiveHookRead(d *schema.ResourceData, meta interface{}) error { + err := checkOrganization(meta) + if err != nil { + return err + } + + repoName := d.Get("repository").(string) + hookName := d.Get("name").(string) + + hook, err := fetchGitHubRepositoryPreReceiveHookByName(meta, repoName, hookName) + if err != nil { + return err + } + + d.Set("enforcement", hook.Enforcement) + + if _, ok := d.GetOk("config_url"); ok { + d.Set("config_url", hook.ConfigURL) + } + + return nil +} + +func resourceGithubRepositoryPreReceiveHookDelete(d *schema.ResourceData, meta interface{}) error { + err := checkOrganization(meta) + if err != nil { + return err + } + + repoName := d.Get("repository").(string) + hookName := d.Get("name").(string) + + hook, err := fetchGitHubRepositoryPreReceiveHookByName(meta, repoName, hookName) + if err != nil { + return err + } + + disabled := "disabled" + hook.Enforcement = &disabled + + ctx := context.Background() + client := meta.(*Organization).client + orgName := meta.(*Organization).name + _, _, err = client.Repositories.UpdatePreReceiveHook(ctx, orgName, repoName, *hook.ID, hook) + if _, _, err = client.Repositories.UpdatePreReceiveHook(ctx, orgName, repoName, *hook.ID, hook); err != nil { + return err + } + + return nil +} diff --git a/github/resource_github_repository_prereceive_hook_test.go b/github/resource_github_repository_prereceive_hook_test.go new file mode 100644 index 0000000000..5fdebc21e6 --- /dev/null +++ b/github/resource_github_repository_prereceive_hook_test.go @@ -0,0 +1,96 @@ +package github + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccGithubRepositoryPreReceiveHook_basic(t *testing.T) { + randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + enforcement := "enabled" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryPreReceiveHookConfig_basic(randString), + Check: resource.ComposeTestCheckFunc( + testAccCheckGitHubPreReceiveHookEnforcement("github_repository_prereceive_hook.foo", enforcement), + resource.TestCheckResourceAttr( + "github_repository_prereceive_hook.foo", "enforcement", enforcement), + resource.TestCheckResourceAttrSet( + "github_repository_prereceive_hook.foo", "id"), + ), + }, + }, + }) +} + +func testAccCheckGitHubPreReceiveHookEnforcement(n string, enforcement string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No hook ID is set") + } + + id := strings.Split(rs.Primary.ID, "/") + orgName, repoName, i := id[0], id[1], id[2] + + hookID, err := strconv.ParseInt(i, 10, 64) + if err != nil { + return err + } + + org := testAccProvider.Meta().(*Organization) + client := org.client + + hook, _, err := client.Repositories.GetPreReceiveHook(context.TODO(), orgName, repoName, hookID) + + if err != nil { + return err + } + + if *hook.Enforcement != enforcement { + return fmt.Errorf("Enforcement set to %s instead of %s", *hook.Enforcement, enforcement) + } + + return nil + } +} + +func testAccGithubRepositoryPreReceiveHookConfig_basic(randString string) string { + return fmt.Sprintf(` +resource "github_repository" "foo" { + name = "foo-%s" + description = "Terraform acceptance tests" + homepage_url = "http://example.com/" + + # So that acceptance tests can be run in a github organization + # with no billing + private = false + + has_issues = true + has_wiki = true + has_downloads = true +} + +resource "github_repository_prereceive_hook" "foo" { + name = "require-code-review" + repository = "${github_repository.foo.name}" + enforcement = "enabled" +} +`, randString) +} diff --git a/website/docs/r/repository_prereceive_hook.html b/website/docs/r/repository_prereceive_hook.html new file mode 100644 index 0000000000..d18530d535 --- /dev/null +++ b/website/docs/r/repository_prereceive_hook.html @@ -0,0 +1,41 @@ +--- +layout: "github" +page_title: "GitHub: github_repository_prereceive_hook" +sidebar_current: "docs-github-resource-repository-prereceive-hook" +description: |- + Creates and manages repository pre-recieve hooks within a GitHub Enterprise organization +--- + +# github_repository_prereceive_hook + +This resource allows you to create and manage [pre-receive hooks](https://developer.github.com/enterprise/2.17/v3/enterprise-admin/repo_pre_receive_hooks/) for +repositories within your GitHub Enterprise organizations. + +~> **NOTE** Pre-recieve hooks are only available on GitHub Enterprise. + +## Example Usage + +```hcl +resource "github_repository" "repo" { + name = "foo" + description = "Terraform acceptance tests" +} + +resource "github_repository_prereceive_hook" "reviews" { + name = "require-code-review" + repository = "${github_repository.repo.name}" + enforcement = "enabled" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the pre-receive hook. + +* `repository` - (Required) The repository of the pre-receive hook. + +* `enforcement` - (Required) The state of enforcement for the hook on this repository. Available keys are `enabled`, `disabled`, and `testing`. + +* `config_url` - (Optional) URL for the endpoint where enforcement is set. diff --git a/website/github.erb b/website/github.erb index 7421b267b7..60fd3e4745 100644 --- a/website/github.erb +++ b/website/github.erb @@ -67,6 +67,9 @@ > github_repository_deploy_key + > + github_repository_prereceive_hook + > github_repository_project