From dcbe8a473607fe6492067e836ef7703fa03d5842 Mon Sep 17 00:00:00 2001 From: Richard Clamp Date: Fri, 28 Apr 2017 11:15:10 +0100 Subject: [PATCH] provider/gitlab: add gitlab_project_hook resource Here we add a new resource type `gitlab_project_hook`. It allows for management of custom hooks for a gitlab project. This is a relatively simple resource as a project hook is a simple association between a project, and a url to hit when one of the flagged events occurs on that project. Hooks (called Webhooks in some user documentation, but simply Hooks in the api documentation) are covered here for users https://docs.gitlab.com/ce/user/project/integrations/webhooks.html and in the API documentation at https://docs.gitlab.com/ce/api/projects.html#hooks --- builtin/providers/gitlab/provider.go | 3 +- .../gitlab/resource_gitlab_project_hook.go | 185 +++++++++++++++ .../resource_gitlab_project_hook_test.go | 220 ++++++++++++++++++ .../gitlab/r/project_hook.html.markdown | 59 +++++ 4 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/gitlab/resource_gitlab_project_hook.go create mode 100644 builtin/providers/gitlab/resource_gitlab_project_hook_test.go create mode 100644 website/source/docs/providers/gitlab/r/project_hook.html.markdown diff --git a/builtin/providers/gitlab/provider.go b/builtin/providers/gitlab/provider.go index 4dfe07f6c..097dc39e1 100644 --- a/builtin/providers/gitlab/provider.go +++ b/builtin/providers/gitlab/provider.go @@ -25,7 +25,8 @@ func Provider() terraform.ResourceProvider { }, }, ResourcesMap: map[string]*schema.Resource{ - "gitlab_project": resourceGitlabProject(), + "gitlab_project": resourceGitlabProject(), + "gitlab_project_hook": resourceGitlabProjectHook(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/gitlab/resource_gitlab_project_hook.go b/builtin/providers/gitlab/resource_gitlab_project_hook.go new file mode 100644 index 000000000..9a82aef51 --- /dev/null +++ b/builtin/providers/gitlab/resource_gitlab_project_hook.go @@ -0,0 +1,185 @@ +package gitlab + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + gitlab "github.com/xanzy/go-gitlab" +) + +func resourceGitlabProjectHook() *schema.Resource { + return &schema.Resource{ + Create: resourceGitlabProjectHookCreate, + Read: resourceGitlabProjectHookRead, + Update: resourceGitlabProjectHookUpdate, + Delete: resourceGitlabProjectHookDelete, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "token": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "push_events": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "issues_events": { + Type: schema.TypeBool, + Optional: true, + }, + "merge_requests_events": { + Type: schema.TypeBool, + Optional: true, + }, + "tag_push_events": { + Type: schema.TypeBool, + Optional: true, + }, + "note_events": { + Type: schema.TypeBool, + Optional: true, + }, + "build_events": { + Type: schema.TypeBool, + Optional: true, + }, + "pipeline_events": { + Type: schema.TypeBool, + Optional: true, + }, + "wiki_page_events": { + Type: schema.TypeBool, + Optional: true, + }, + "enable_ssl_verification": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func resourceGitlabProjectHookCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*gitlab.Client) + project := d.Get("project").(string) + options := &gitlab.AddProjectHookOptions{ + URL: gitlab.String(d.Get("url").(string)), + PushEvents: gitlab.Bool(d.Get("push_events").(bool)), + IssuesEvents: gitlab.Bool(d.Get("issues_events").(bool)), + MergeRequestsEvents: gitlab.Bool(d.Get("merge_requests_events").(bool)), + TagPushEvents: gitlab.Bool(d.Get("tag_push_events").(bool)), + NoteEvents: gitlab.Bool(d.Get("note_events").(bool)), + BuildEvents: gitlab.Bool(d.Get("build_events").(bool)), + PipelineEvents: gitlab.Bool(d.Get("pipeline_events").(bool)), + WikiPageEvents: gitlab.Bool(d.Get("wiki_page_events").(bool)), + EnableSSLVerification: gitlab.Bool(d.Get("enable_ssl_verification").(bool)), + } + + if v, ok := d.GetOk("token"); ok { + options.Token = gitlab.String(v.(string)) + } + + log.Printf("[DEBUG] create gitlab project hook %q", options.URL) + + hook, _, err := client.Projects.AddProjectHook(project, options) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("%d", hook.ID)) + + return resourceGitlabProjectHookRead(d, meta) +} + +func resourceGitlabProjectHookRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*gitlab.Client) + project := d.Get("project").(string) + hookId, err := strconv.Atoi(d.Id()) + if err != nil { + return err + } + log.Printf("[DEBUG] read gitlab project hook %s/%d", project, hookId) + + hook, response, err := client.Projects.GetProjectHook(project, hookId) + if err != nil { + if response.StatusCode == 404 { + log.Printf("[WARN] removing project hook %d from state because it no longer exists in gitlab", hookId) + d.SetId("") + return nil + } + + return err + } + + d.Set("url", hook.URL) + d.Set("push_events", hook.PushEvents) + d.Set("issues_events", hook.IssuesEvents) + d.Set("merge_requests_events", hook.MergeRequestsEvents) + d.Set("tag_push_events", hook.TagPushEvents) + d.Set("note_events", hook.NoteEvents) + d.Set("build_events", hook.BuildEvents) + d.Set("pipeline_events", hook.PipelineEvents) + d.Set("wiki_page_events", hook.WikiPageEvents) + d.Set("enable_ssl_verification", hook.EnableSSLVerification) + return nil +} + +func resourceGitlabProjectHookUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*gitlab.Client) + project := d.Get("project").(string) + hookId, err := strconv.Atoi(d.Id()) + if err != nil { + return err + } + options := &gitlab.EditProjectHookOptions{ + URL: gitlab.String(d.Get("url").(string)), + PushEvents: gitlab.Bool(d.Get("push_events").(bool)), + IssuesEvents: gitlab.Bool(d.Get("issues_events").(bool)), + MergeRequestsEvents: gitlab.Bool(d.Get("merge_requests_events").(bool)), + TagPushEvents: gitlab.Bool(d.Get("tag_push_events").(bool)), + NoteEvents: gitlab.Bool(d.Get("note_events").(bool)), + BuildEvents: gitlab.Bool(d.Get("build_events").(bool)), + PipelineEvents: gitlab.Bool(d.Get("pipeline_events").(bool)), + WikiPageEvents: gitlab.Bool(d.Get("wiki_page_events").(bool)), + EnableSSLVerification: gitlab.Bool(d.Get("enable_ssl_verification").(bool)), + } + + if d.HasChange("token") { + options.Token = gitlab.String(d.Get("token").(string)) + } + + log.Printf("[DEBUG] update gitlab project hook %s", d.Id()) + + _, _, err = client.Projects.EditProjectHook(project, hookId, options) + if err != nil { + return err + } + + return resourceGitlabProjectHookRead(d, meta) +} + +func resourceGitlabProjectHookDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*gitlab.Client) + project := d.Get("project").(string) + hookId, err := strconv.Atoi(d.Id()) + if err != nil { + return err + } + log.Printf("[DEBUG] Delete gitlab project hook %s", d.Id()) + + _, err = client.Projects.DeleteProjectHook(project, hookId) + return err +} diff --git a/builtin/providers/gitlab/resource_gitlab_project_hook_test.go b/builtin/providers/gitlab/resource_gitlab_project_hook_test.go new file mode 100644 index 000000000..0d7d433d7 --- /dev/null +++ b/builtin/providers/gitlab/resource_gitlab_project_hook_test.go @@ -0,0 +1,220 @@ +package gitlab + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/xanzy/go-gitlab" +) + +func TestAccGitlabProjectHook_basic(t *testing.T) { + var hook gitlab.ProjectHook + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGitlabProjectHookDestroy, + Steps: []resource.TestStep{ + // Create a project and hook with default options + { + Config: testAccGitlabProjectHookConfig(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectHookExists("gitlab_project_hook.foo", &hook), + testAccCheckGitlabProjectHookAttributes(&hook, &testAccGitlabProjectHookExpectedAttributes{ + URL: fmt.Sprintf("https://example.com/hook-%d", rInt), + PushEvents: true, + EnableSSLVerification: true, + }), + ), + }, + // Update the project hook to toggle all the values to their inverse + { + Config: testAccGitlabProjectHookUpdateConfig(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectHookExists("gitlab_project_hook.foo", &hook), + testAccCheckGitlabProjectHookAttributes(&hook, &testAccGitlabProjectHookExpectedAttributes{ + URL: fmt.Sprintf("https://example.com/hook-%d", rInt), + PushEvents: false, + IssuesEvents: true, + MergeRequestsEvents: true, + TagPushEvents: true, + NoteEvents: true, + BuildEvents: true, + PipelineEvents: true, + WikiPageEvents: true, + EnableSSLVerification: false, + }), + ), + }, + // Update the project hook to toggle the options back + { + Config: testAccGitlabProjectHookConfig(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectHookExists("gitlab_project_hook.foo", &hook), + testAccCheckGitlabProjectHookAttributes(&hook, &testAccGitlabProjectHookExpectedAttributes{ + URL: fmt.Sprintf("https://example.com/hook-%d", rInt), + PushEvents: true, + EnableSSLVerification: true, + }), + ), + }, + }, + }) +} + +func testAccCheckGitlabProjectHookExists(n string, hook *gitlab.ProjectHook) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not Found: %s", n) + } + + hookID, err := strconv.Atoi(rs.Primary.ID) + if err != nil { + return err + } + repoName := rs.Primary.Attributes["project"] + if repoName == "" { + return fmt.Errorf("No project ID is set") + } + conn := testAccProvider.Meta().(*gitlab.Client) + + gotHook, _, err := conn.Projects.GetProjectHook(repoName, hookID) + if err != nil { + return err + } + *hook = *gotHook + return nil + } +} + +type testAccGitlabProjectHookExpectedAttributes struct { + URL string + PushEvents bool + IssuesEvents bool + MergeRequestsEvents bool + TagPushEvents bool + NoteEvents bool + BuildEvents bool + PipelineEvents bool + WikiPageEvents bool + EnableSSLVerification bool +} + +func testAccCheckGitlabProjectHookAttributes(hook *gitlab.ProjectHook, want *testAccGitlabProjectHookExpectedAttributes) resource.TestCheckFunc { + return func(s *terraform.State) error { + if hook.URL != want.URL { + return fmt.Errorf("got url %q; want %q", hook.URL, want.URL) + } + + if hook.EnableSSLVerification != want.EnableSSLVerification { + return fmt.Errorf("got enable_ssl_verification %t; want %t", hook.EnableSSLVerification, want.EnableSSLVerification) + } + + if hook.PushEvents != want.PushEvents { + return fmt.Errorf("got push_events %t; want %t", hook.PushEvents, want.PushEvents) + } + + if hook.IssuesEvents != want.IssuesEvents { + return fmt.Errorf("got issues_events %t; want %t", hook.IssuesEvents, want.IssuesEvents) + } + + if hook.MergeRequestsEvents != want.MergeRequestsEvents { + return fmt.Errorf("got merge_requests_events %t; want %t", hook.MergeRequestsEvents, want.MergeRequestsEvents) + } + + if hook.TagPushEvents != want.TagPushEvents { + return fmt.Errorf("got tag_push_events %t; want %t", hook.TagPushEvents, want.TagPushEvents) + } + + if hook.NoteEvents != want.NoteEvents { + return fmt.Errorf("got note_events %t; want %t", hook.NoteEvents, want.NoteEvents) + } + + if hook.BuildEvents != want.BuildEvents { + return fmt.Errorf("got build_events %t; want %t", hook.BuildEvents, want.BuildEvents) + } + + if hook.PipelineEvents != want.PipelineEvents { + return fmt.Errorf("got pipeline_events %t; want %t", hook.PipelineEvents, want.PipelineEvents) + } + + if hook.WikiPageEvents != want.WikiPageEvents { + return fmt.Errorf("got wiki_page_events %t; want %t", hook.WikiPageEvents, want.WikiPageEvents) + } + + return nil + } +} + +func testAccCheckGitlabProjectHookDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*gitlab.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "gitlab_project" { + continue + } + + gotRepo, resp, err := conn.Projects.GetProject(rs.Primary.ID) + if err == nil { + if gotRepo != nil && fmt.Sprintf("%d", gotRepo.ID) == rs.Primary.ID { + return fmt.Errorf("Repository still exists") + } + } + if resp.StatusCode != 404 { + return err + } + return nil + } + return nil +} + +func testAccGitlabProjectHookConfig(rInt int) string { + return fmt.Sprintf(` +resource "gitlab_project" "foo" { + name = "foo-%d" + description = "Terraform acceptance tests" + + # So that acceptance tests can be run in a gitlab organization + # with no billing + visibility_level = "public" +} + +resource "gitlab_project_hook" "foo" { + project = "${gitlab_project.foo.id}" + url = "https://example.com/hook-%d" +} + `, rInt, rInt) +} + +func testAccGitlabProjectHookUpdateConfig(rInt int) string { + return fmt.Sprintf(` +resource "gitlab_project" "foo" { + name = "foo-%d" + description = "Terraform acceptance tests" + + # So that acceptance tests can be run in a gitlab organization + # with no billing + visibility_level = "public" +} + +resource "gitlab_project_hook" "foo" { + project = "${gitlab_project.foo.id}" + url = "https://example.com/hook-%d" + enable_ssl_verification = false + push_events = false + issues_events = true + merge_requests_events = true + tag_push_events = true + note_events = true + build_events = true + pipeline_events = true + wiki_page_events = true +} + `, rInt, rInt) +} diff --git a/website/source/docs/providers/gitlab/r/project_hook.html.markdown b/website/source/docs/providers/gitlab/r/project_hook.html.markdown new file mode 100644 index 000000000..e117efc63 --- /dev/null +++ b/website/source/docs/providers/gitlab/r/project_hook.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "gitlab" +page_title: "GitLab: gitlab_project_hook" +sidebar_current: "docs-gitlab-resource-project-hook" +description: |- +Creates and manages hooks for GitLab projects +--- + +# gitlab\_project\_hook + +This resource allows you to create and manage hooks for your GitLab projects. +For further information on hooks, consult the [gitlab +documentation](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html). + + +## Example Usage + +```hcl +resource "gitlab_project_hook" "example" { + project = "example/hooked" + url = "https://example.com/hook/example" + merge_requests_events = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `project` - (Required) The name or id of the project to add the hook to. + +* `url` - (Required) The url of the hook to invoke. + +* `token` - (Optional) A token to present when invoking the hook. + +* `enable_ssl_verification` - (Optional) Enable ssl verification when invoking +the hook. + +* `push_events` - (Optional) Invoke the hook for push events. + +* `issues_events` - (Optional) Invoke the hook for issues events. + +* `merge_requests_events` - (Optional) Invoke the hook for merge requests. + +* `tag_push_events` - (Optional) Invoke the hook for tag push events. + +* `note_events` - (Optional) Invoke the hook for tag push events. + +* `build_events` - (Optional) Invoke the hook for build events. + +* `pipeline_events` - (Optional) Invoke the hook for pipeline events. + +* `wiki_page_events` - (Optional) Invoke the hook for wiki page events. + +## Attributes Reference + +The resource exports the following attributes: + +* `id` - The unique id assigned to the hook by the GitLab server.