provider/github: add repository_webhook resource (#12924)

* provider/github: add repository_webhook resource

`repository_webhook` can be used to manage webhooks for repositories.
It is currently limited to organization repositories only.

The changeset includes both documentation and tests.
The tests are green:

```
make testacc TEST=./builtin/providers/github
TESTARGS='-run=TestAccGithubRepositoryWebhook_basic'
==> Checking that code complies with gofmt requirements...
go generate $(go list ./... | grep -v /terraform/vendor/)
2017/03/21 16:20:07 Generated command/internal_plugin_list.go
TF_ACC=1 go test ./builtin/providers/github -v
-run=TestAccGithubRepositoryWebhook_basic -timeout 120m
=== RUN   TestAccGithubRepositoryWebhook_basic
--- PASS: TestAccGithubRepositoryWebhook_basic (5.10s)
PASS
ok      github.com/hashicorp/terraform/builtin/providers/github    5.113s
```

* provider/github: add github_organization_webhook

the `github_organization_webhook` resource is similar to the
`github_repository_webhook` resource, but it manages webhooks for an
organization.

the tests are green:

```
make testacc TEST=./builtin/providers/github
TESTARGS='-run=TestAccGithubOrganizationWebhook'
==> Checking that code complies with gofmt requirements...
go generate $(go list ./... | grep -v /terraform/vendor/)
2017/03/21 17:23:33 Generated command/internal_plugin_list.go
TF_ACC=1 go test ./builtin/providers/github -v
-run=TestAccGithubOrganizationWebhook -timeout 120m
=== RUN   TestAccGithubOrganizationWebhook_basic
--- PASS: TestAccGithubOrganizationWebhook_basic (2.09s)
PASS
ok      github.com/hashicorp/terraform/builtin/providers/github    2.109s
```
This commit is contained in:
Raphael Randschau 2017-03-23 10:21:45 +01:00 committed by Paul Stack
parent eddc8cce37
commit 6991ceb2e2
7 changed files with 749 additions and 0 deletions

View File

@ -37,6 +37,8 @@ func Provider() terraform.ResourceProvider {
"github_team_repository": resourceGithubTeamRepository(),
"github_membership": resourceGithubMembership(),
"github_repository": resourceGithubRepository(),
"github_repository_webhook": resourceGithubRepositoryWebhook(),
"github_organization_webhook": resourceGithubOrganizationWebhook(),
"github_repository_collaborator": resourceGithubRepositoryCollaborator(),
"github_issue_label": resourceGithubIssueLabel(),
},

View File

@ -0,0 +1,137 @@
package github
import (
"context"
"fmt"
"strconv"
"github.com/google/go-github/github"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceGithubOrganizationWebhook() *schema.Resource {
return &schema.Resource{
Create: resourceGithubOrganizationWebhookCreate,
Read: resourceGithubOrganizationWebhookRead,
Update: resourceGithubOrganizationWebhookUpdate,
Delete: resourceGithubOrganizationWebhookDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateGithubOrganizationWebhookName,
},
"events": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"configuration": {
Type: schema.TypeMap,
Optional: true,
},
"url": {
Type: schema.TypeString,
Computed: true,
},
"active": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}
}
func validateGithubOrganizationWebhookName(v interface{}, k string) (ws []string, errors []error) {
if v.(string) != "web" {
errors = append(errors, fmt.Errorf("Github: name can only be web"))
}
return
}
func resourceGithubOrganizationWebhookObject(d *schema.ResourceData) *github.Hook {
url := d.Get("url").(string)
active := d.Get("active").(bool)
events := []string{}
eventSet := d.Get("events").(*schema.Set)
for _, v := range eventSet.List() {
events = append(events, v.(string))
}
name := d.Get("name").(string)
hook := &github.Hook{
Name: &name,
URL: &url,
Events: events,
Active: &active,
Config: d.Get("configuration").(map[string]interface{}),
}
return hook
}
func resourceGithubOrganizationWebhookCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hk := resourceGithubOrganizationWebhookObject(d)
hook, _, err := client.Organizations.CreateHook(context.TODO(), meta.(*Organization).name, hk)
if err != nil {
return err
}
d.SetId(strconv.Itoa(*hook.ID))
return resourceGithubOrganizationWebhookRead(d, meta)
}
func resourceGithubOrganizationWebhookRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hookID, _ := strconv.Atoi(d.Id())
hook, resp, err := client.Organizations.GetHook(context.TODO(), meta.(*Organization).name, hookID)
if err != nil {
if resp.StatusCode == 404 {
d.SetId("")
return nil
}
return err
}
d.Set("name", hook.Name)
d.Set("url", hook.URL)
d.Set("active", hook.Active)
d.Set("events", hook.Events)
d.Set("configuration", hook.Config)
return nil
}
func resourceGithubOrganizationWebhookUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hk := resourceGithubOrganizationWebhookObject(d)
hookID, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
_, _, err = client.Organizations.EditHook(context.TODO(), meta.(*Organization).name, hookID, hk)
if err != nil {
return err
}
return resourceGithubOrganizationWebhookRead(d, meta)
}
func resourceGithubOrganizationWebhookDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hookID, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
_, err = client.Organizations.DeleteHook(context.TODO(), meta.(*Organization).name, hookID)
return err
}

View File

@ -0,0 +1,166 @@
package github
import (
"context"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"github.com/google/go-github/github"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccGithubOrganizationWebhook_basic(t *testing.T) {
var hook github.Hook
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckGithubOrganizationWebhookDestroy,
Steps: []resource.TestStep{
{
Config: testAccGithubOrganizationWebhookConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckGithubOrganizationWebhookExists("github_organization_webhook.foo", &hook),
testAccCheckGithubOrganizationWebhookAttributes(&hook, &testAccGithubOrganizationWebhookExpectedAttributes{
Name: "web",
Events: []string{"pull_request"},
Configuration: map[string]interface{}{
"url": "https://google.de/webhook",
"content_type": "json",
"insecure_ssl": "1",
},
Active: true,
}),
),
},
{
Config: testAccGithubOrganizationWebhookUpdateConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckGithubOrganizationWebhookExists("github_organization_webhook.foo", &hook),
testAccCheckGithubOrganizationWebhookAttributes(&hook, &testAccGithubOrganizationWebhookExpectedAttributes{
Name: "web",
Events: []string{"issues"},
Configuration: map[string]interface{}{
"url": "https://google.de/webhooks",
"content_type": "form",
"insecure_ssl": "0",
},
Active: false,
}),
),
},
},
})
}
func testAccCheckGithubOrganizationWebhookExists(n string, hook *github.Hook) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not Found: %s", n)
}
hookID, _ := strconv.Atoi(rs.Primary.ID)
if hookID == 0 {
return fmt.Errorf("No repository name is set")
}
org := testAccProvider.Meta().(*Organization)
conn := org.client
getHook, _, err := conn.Organizations.GetHook(context.TODO(), org.name, hookID)
if err != nil {
return err
}
*hook = *getHook
return nil
}
}
type testAccGithubOrganizationWebhookExpectedAttributes struct {
Name string
Events []string
Configuration map[string]interface{}
Active bool
}
func testAccCheckGithubOrganizationWebhookAttributes(hook *github.Hook, want *testAccGithubOrganizationWebhookExpectedAttributes) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *hook.Name != want.Name {
return fmt.Errorf("got hook %q; want %q", *hook.Name, want.Name)
}
if *hook.Active != want.Active {
return fmt.Errorf("got hook %t; want %t", *hook.Active, want.Active)
}
if !strings.HasPrefix(*hook.URL, "https://") {
return fmt.Errorf("got http URL %q; want to start with 'https://'", *hook.URL)
}
if !reflect.DeepEqual(hook.Events, want.Events) {
return fmt.Errorf("got hook events %q; want %q", hook.Events, want.Events)
}
if !reflect.DeepEqual(hook.Config, want.Configuration) {
return fmt.Errorf("got hook configuration %q; want %q", hook.Config, want.Configuration)
}
return nil
}
}
func testAccCheckGithubOrganizationWebhookDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*Organization).client
orgName := testAccProvider.Meta().(*Organization).name
for _, rs := range s.RootModule().Resources {
if rs.Type != "github_organization_webhook" {
continue
}
id, err := strconv.Atoi(rs.Primary.ID)
if err != nil {
return err
}
gotHook, resp, err := conn.Organizations.GetHook(context.TODO(), orgName, id)
if err == nil {
if gotHook != nil && *gotHook.ID == id {
return fmt.Errorf("Webhook still exists")
}
}
if resp.StatusCode != 404 {
return err
}
return nil
}
return nil
}
const testAccGithubOrganizationWebhookConfig = `
resource "github_organization_webhook" "foo" {
name = "web"
configuration {
url = "https://google.de/webhook"
content_type = "json"
insecure_ssl = true
}
events = ["pull_request"]
}
`
const testAccGithubOrganizationWebhookUpdateConfig = `
resource "github_organization_webhook" "foo" {
name = "web"
configuration {
url = "https://google.de/webhooks"
content_type = "form"
insecure_ssl = false
}
active = false
events = ["issues"]
}
`

View File

@ -0,0 +1,132 @@
package github
import (
"context"
"strconv"
"github.com/google/go-github/github"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceGithubRepositoryWebhook() *schema.Resource {
return &schema.Resource{
Create: resourceGithubRepositoryWebhookCreate,
Read: resourceGithubRepositoryWebhookRead,
Update: resourceGithubRepositoryWebhookUpdate,
Delete: resourceGithubRepositoryWebhookDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"events": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"configuration": {
Type: schema.TypeMap,
Optional: true,
},
"url": {
Type: schema.TypeString,
Computed: true,
},
"active": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}
}
func resourceGithubRepositoryWebhookObject(d *schema.ResourceData) *github.Hook {
url := d.Get("url").(string)
active := d.Get("active").(bool)
events := []string{}
eventSet := d.Get("events").(*schema.Set)
for _, v := range eventSet.List() {
events = append(events, v.(string))
}
name := d.Get("name").(string)
hook := &github.Hook{
Name: &name,
URL: &url,
Events: events,
Active: &active,
Config: d.Get("configuration").(map[string]interface{}),
}
return hook
}
func resourceGithubRepositoryWebhookCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hk := resourceGithubRepositoryWebhookObject(d)
hook, _, err := client.Repositories.CreateHook(context.TODO(), meta.(*Organization).name, d.Get("repository").(string), hk)
if err != nil {
return err
}
d.SetId(strconv.Itoa(*hook.ID))
return resourceGithubRepositoryWebhookRead(d, meta)
}
func resourceGithubRepositoryWebhookRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hookID, _ := strconv.Atoi(d.Id())
hook, resp, err := client.Repositories.GetHook(context.TODO(), meta.(*Organization).name, d.Get("repository").(string), hookID)
if err != nil {
if resp.StatusCode == 404 {
d.SetId("")
return nil
}
return err
}
d.Set("name", hook.Name)
d.Set("url", hook.URL)
d.Set("active", hook.Active)
d.Set("events", hook.Events)
d.Set("configuration", hook.Config)
return nil
}
func resourceGithubRepositoryWebhookUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hk := resourceGithubRepositoryWebhookObject(d)
hookID, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
_, _, err = client.Repositories.EditHook(context.TODO(), meta.(*Organization).name, d.Get("repository").(string), hookID, hk)
if err != nil {
return err
}
return resourceGithubRepositoryWebhookRead(d, meta)
}
func resourceGithubRepositoryWebhookDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Organization).client
hookID, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
_, err = client.Repositories.DeleteHook(context.TODO(), meta.(*Organization).name, d.Get("repository").(string), hookID)
return err
}

View File

@ -0,0 +1,206 @@
package github
import (
"context"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"github.com/google/go-github/github"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccGithubRepositoryWebhook_basic(t *testing.T) {
randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
var hook github.Hook
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckGithubRepositoryWebhookDestroy,
Steps: []resource.TestStep{
{
Config: testAccGithubRepositoryWebhookConfig(randString),
Check: resource.ComposeTestCheckFunc(
testAccCheckGithubRepositoryWebhookExists("github_repository_webhook.foo", fmt.Sprintf("foo-%s", randString), &hook),
testAccCheckGithubRepositoryWebhookAttributes(&hook, &testAccGithubRepositoryWebhookExpectedAttributes{
Name: "web",
Events: []string{"pull_request"},
Configuration: map[string]interface{}{
"url": "https://google.de/webhook",
"content_type": "json",
"insecure_ssl": "1",
},
Active: true,
}),
),
},
{
Config: testAccGithubRepositoryWebhookUpdateConfig(randString),
Check: resource.ComposeTestCheckFunc(
testAccCheckGithubRepositoryWebhookExists("github_repository_webhook.foo", fmt.Sprintf("foo-%s", randString), &hook),
testAccCheckGithubRepositoryWebhookAttributes(&hook, &testAccGithubRepositoryWebhookExpectedAttributes{
Name: "web",
Events: []string{"issues"},
Configuration: map[string]interface{}{
"url": "https://google.de/webhooks",
"content_type": "form",
"insecure_ssl": "0",
},
Active: false,
}),
),
},
},
})
}
func testAccCheckGithubRepositoryWebhookExists(n string, repoName string, hook *github.Hook) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not Found: %s", n)
}
hookID, _ := strconv.Atoi(rs.Primary.ID)
if hookID == 0 {
return fmt.Errorf("No repository name is set")
}
org := testAccProvider.Meta().(*Organization)
conn := org.client
getHook, _, err := conn.Repositories.GetHook(context.TODO(), org.name, repoName, hookID)
if err != nil {
return err
}
*hook = *getHook
return nil
}
}
type testAccGithubRepositoryWebhookExpectedAttributes struct {
Name string
Events []string
Configuration map[string]interface{}
Active bool
}
func testAccCheckGithubRepositoryWebhookAttributes(hook *github.Hook, want *testAccGithubRepositoryWebhookExpectedAttributes) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *hook.Name != want.Name {
return fmt.Errorf("got hook %q; want %q", *hook.Name, want.Name)
}
if *hook.Active != want.Active {
return fmt.Errorf("got hook %t; want %t", *hook.Active, want.Active)
}
if !strings.HasPrefix(*hook.URL, "https://") {
return fmt.Errorf("got http URL %q; want to start with 'https://'", *hook.URL)
}
if !reflect.DeepEqual(hook.Events, want.Events) {
return fmt.Errorf("got hook events %q; want %q", hook.Events, want.Events)
}
if !reflect.DeepEqual(hook.Config, want.Configuration) {
return fmt.Errorf("got hook configuration %q; want %q", hook.Config, want.Configuration)
}
return nil
}
}
func testAccCheckGithubRepositoryWebhookDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*Organization).client
orgName := testAccProvider.Meta().(*Organization).name
for _, rs := range s.RootModule().Resources {
if rs.Type != "github_repository_webhook" {
continue
}
id, err := strconv.Atoi(rs.Primary.ID)
if err != nil {
return err
}
gotHook, resp, err := conn.Repositories.GetHook(context.TODO(), orgName, rs.Primary.Attributes["repository"], id)
if err == nil {
if gotHook != nil && *gotHook.ID == id {
return fmt.Errorf("Webhook still exists")
}
}
if resp.StatusCode != 404 {
return err
}
return nil
}
return nil
}
func testAccGithubRepositoryWebhookConfig(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_webhook" "foo" {
depends_on = ["github_repository.foo"]
repository = "foo-%s"
name = "web"
configuration {
url = "https://google.de/webhook"
content_type = "json"
insecure_ssl = true
}
events = ["pull_request"]
}
`, randString, randString)
}
func testAccGithubRepositoryWebhookUpdateConfig(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_webhook" "foo" {
depends_on = ["github_repository.foo"]
repository = "foo-%s"
name = "web"
configuration {
url = "https://google.de/webhooks"
content_type = "form"
insecure_ssl = false
}
active = false
events = ["issues"]
}
`, randString, randString)
}

View File

@ -0,0 +1,45 @@
---
layout: "github"
page_title: "GitHub: github_organization_webhook"
sidebar_current: "docs-github-resource-organization-webhook"
description: |-
Creates and manages webhooks for Github organizations
---
# github\_organization\_webhook
This resource allows you to create and manage webhooks for Github organization.
## Example Usage
```
resource "github_organization_webhook" "foo" {
name = "web"
configuration {
url = "https://google.de/"
content_type = "form"
insecure_ssl = false
}
active = false
events = ["issues"]
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The type of the webhook. See a list of [available hooks](https://api.github.com/hooks).
* `events` - (Required) A list of events which should trigger the webhook. Defaults to `["push"]`. See a list of [available events](https://developer.github.com/v3/activity/events/types/)
* `config` - (Required) key/value pair of configuration for this webhook. Available keys are `url`, `content_type`, `secret` and `insecure_ssl`.
* `active` - (Optional) Indicate of the webhook should receive events. Defaults to `true`.
## Attributes Reference
The following additional attributes are exported:
* `url` - URL of the webhook

View File

@ -0,0 +1,61 @@
---
layout: "github"
page_title: "GitHub: github_repository_webhook"
sidebar_current: "docs-github-resource-repository-webhook"
description: |-
Creates and manages repository webhooks within Github organizations
---
# github\_repository\_webhook
This resource allows you to create and manage webhooks for repositories within your
Github organization.
This resource cannot currently be used to manage webhooks for *personal* repositories,
outside of organizations.
## Example Usage
```
resource "github_repository" "repo" {
name = "foo"
description = "Terraform acceptance tests"
homepage_url = "http://example.com/"
private = false
}
resource "github_repository_webhook" "foo" {
repository = "${github_repository.repo.name}"
name = "web"
configuration {
url = "https://google.de/"
content_type = "form"
insecure_ssl = false
}
active = false
events = ["issues"]
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The type of the webhook. See a list of [available hooks](https://api.github.com/hooks).
* `repository` - (Required) The repository of the webhook.
* `events` - (Required) A list of events which should trigger the webhook. Defaults to `["push"]`. See a list of [available events](https://developer.github.com/v3/activity/events/types/)
* `config` - (Required) key/value pair of configuration for this webhook. Available keys are `url`, `content_type`, `secret` and `insecure_ssl`.
* `active` - (Optional) Indicate of the webhook should receive events. Defaults to `true`.
## Attributes Reference
The following additional attributes are exported:
* `url` - URL of the webhook