Skip to content

Commit

Permalink
fix: Github Environments Policy feature causing the provider to produ…
Browse files Browse the repository at this point in the history
…ce inconsistent result (#1799)

* Add deployment policy resource

- Add the initial code to manage the resource
- Add sample configuration used to test it

TODO
- Documentation
- Tests

* Add schema description

* Fix creation of resource ID

* Add tests

* Add documentation

* Add terraform import support

* Undo example add

* Fix formatting

* PR feedback

* fix: environment branch policy failing to find the created resource

The `Read` operation of the Environments Branch Policy resource
was failing to find the newly created Branch policies, due to
wrongly encoded environment name. Which cause the provider to
be inconsistent.

This fix uses `url.PathEscape` instead of `url.QueryEscape`
since we are using path parameters with the Github API in
that case. Additionally 2 operations - `Read` and `Delete`
don't need to use it as they receive the environment name
already parsed and attempting to encode it again breaks the
name.

* Fix incorrect merge

---------

Co-authored-by: Massimiliano Donini <massimiliano.donini@gmail.com>
Co-authored-by: Keegan Campbell <me@kfcampbell.com>
  • Loading branch information
3 people committed Jul 24, 2023
1 parent dc087bd commit 64f123a
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 1 deletion.
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func Provider() terraform.ResourceProvider {
"github_repository_deploy_key": resourceGithubRepositoryDeployKey(),
"github_repository_deployment_branch_policy": resourceGithubRepositoryDeploymentBranchPolicy(),
"github_repository_environment": resourceGithubRepositoryEnvironment(),
"github_repository_environment_deployment_policy": resourceGithubRepositoryEnvironmentDeploymentPolicy(),
"github_repository_file": resourceGithubRepositoryFile(),
"github_repository_milestone": resourceGithubRepositoryMilestone(),
"github_repository_project": resourceGithubRepositoryProject(),
Expand Down
157 changes: 157 additions & 0 deletions github/resource_github_repository_environment_deployment_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package github

import (
"context"
"log"
"net/http"
"net/url"
"strconv"

"github.com/google/go-github/v53/github"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceGithubRepositoryEnvironmentDeploymentPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceGithubRepositoryEnvironmentDeploymentPolicyCreate,
Read: resourceGithubRepositoryEnvironmentDeploymentPolicyRead,
Update: resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate,
Delete: resourceGithubRepositoryEnvironmentDeploymentPolicyDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The name of the repository. The name is not case sensitive.",
},
"environment": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The name of the environment.",
},
"branch_pattern": {
Type: schema.TypeString,
Required: true,
ForceNew: false,
Description: "The name pattern that branches must match in order to deploy to the environment.",
},
},
}

}

func resourceGithubRepositoryEnvironmentDeploymentPolicyCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.Background()

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
envName := d.Get("environment").(string)
branchPattern := d.Get("branch_pattern").(string)
escapedEnvName := url.PathEscape(envName)

createData := github.DeploymentBranchPolicyRequest{
Name: github.String(branchPattern),
}

resultKey, _, err := client.Repositories.CreateDeploymentBranchPolicy(ctx, owner, repoName, escapedEnvName, &createData)
if err != nil {
return err
}

d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(resultKey.GetID(), 10)))
return resourceGithubRepositoryEnvironmentDeploymentPolicyRead(d, meta)
}

func resourceGithubRepositoryEnvironmentDeploymentPolicyRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.WithValue(context.Background(), ctxId, d.Id())

owner := meta.(*Owner).name
repoName, envName, branchPolicyIdString, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId")
if err != nil {
return err
}

branchPolicyId, err := strconv.ParseInt(branchPolicyIdString, 10, 64)
if err != nil {
return err
}

branchPolicy, _, err := client.Repositories.GetDeploymentBranchPolicy(ctx, owner, repoName, envName, branchPolicyId)
if err != nil {
if ghErr, ok := err.(*github.ErrorResponse); ok {
if ghErr.Response.StatusCode == http.StatusNotModified {
return nil
}
if ghErr.Response.StatusCode == http.StatusNotFound {
log.Printf("[INFO] Removing branch deployment policy for %s/%s/%s from state because it no longer exists in GitHub",
owner, repoName, envName)
d.SetId("")
return nil
}
}
return err
}

d.Set("branch_pattern", branchPolicy.GetName())
return nil
}

func resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.Background()

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
envName := d.Get("environment").(string)
branchPattern := d.Get("branch_pattern").(string)
escapedEnvName := url.PathEscape(envName)
_, _, branchPolicyIdString, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId")
if err != nil {
return err
}

branchPolicyId, err := strconv.ParseInt(branchPolicyIdString, 10, 64)
if err != nil {
return err
}

updateData := github.DeploymentBranchPolicyRequest{
Name: github.String(branchPattern),
}

resultKey, _, err := client.Repositories.UpdateDeploymentBranchPolicy(ctx, owner, repoName, escapedEnvName, branchPolicyId, &updateData)
if err != nil {
return err
}
d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(resultKey.GetID(), 10)))
return resourceGithubRepositoryEnvironmentDeploymentPolicyRead(d, meta)
}

func resourceGithubRepositoryEnvironmentDeploymentPolicyDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.Background()

owner := meta.(*Owner).name
repoName, envName, branchPolicyIdString, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId")
if err != nil {
return err
}

branchPolicyId, err := strconv.ParseInt(branchPolicyIdString, 10, 64)
if err != nil {
return err
}

_, err = client.Repositories.DeleteDeploymentBranchPolicy(ctx, owner, repoName, envName, branchPolicyId)
if err != nil {
return err
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package github

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccGithubRepositoryEnvironmentDeploymentPolicy(t *testing.T) {

randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

t.Run("creates a repository environment with deployment policy", func(t *testing.T) {

config := fmt.Sprintf(`
data "github_user" "current" {
username = ""
}
resource "github_repository" "test" {
name = "tf-acc-test-%s"
}
resource "github_repository_environment" "test" {
repository = github_repository.test.name
environment = "environment/test"
wait_timer = 10000
reviewers {
users = [data.github_user.current.id]
}
deployment_branch_policy {
protected_branches = false
custom_branch_policies = true
}
}
resource "github_repository_environment_deployment_policy" "test" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
branch_pattern = "releases/*"
}
`, randomID)

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_repository_environment_deployment_policy.test", "repository",
fmt.Sprintf("tf-acc-test-%s", randomID),
),
resource.TestCheckResourceAttr(
"github_repository_environment_deployment_policy.test", "environment",
"environment/test",
),
resource.TestCheckResourceAttr(
"github_repository_environment_deployment_policy.test", "branch_pattern",
"releases/*",
),
)

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) {
testCase(t, individual)
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})

})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
layout: "github"
page_title: "GitHub: github_repository_environment_deployment_policy"
description: |-
Creates and manages environment deployment branch policies for GitHub repositories
---

# github_repository_environment_deployment_policy

This resource allows you to create and manage environment deployment branch policies for a GitHub repository.

## Example Usage

```hcl
data "github_user" "current" {
username = ""
}
resource "github_repository" "test" {
name = "tf-acc-test-%s"
}
resource "github_repository_environment" "test" {
repository = github_repository.test.name
environment = "environment/test"
wait_timer = 10000
reviewers {
users = [data.github_user.current.id]
}
deployment_branch_policy {
protected_branches = false
custom_branch_policies = true
}
}
resource "github_repository_environment_deployment_policy" "test" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
branch_pattern = "releases/*"
}
```

## Argument Reference

The following arguments are supported:

* `environment` - (Required) The name of the environment.

* `repository` - (Required) The repository of the environment.

* `branch_pattern` - (Required) The name pattern that branches must match in order to deploy to the environment.


## Import

GitHub Repository Environment Deployment Policy can be imported using an ID made up of `name` of the repository combined with the `environment` name of the environment with the `Id` of the deployment policy, separated by a `:` character, e.g.

```
$ terraform import github_repository_environment.daily terraform:daily:123456
```
5 changes: 4 additions & 1 deletion website/github.erb
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,14 @@
<li>
<a href="/docs/providers/github/r/repository_environment.html">github_repository_environment</a>
</li>
<li>
<a href="/docs/providers/github/r/repository_environment_deployment_policy.html">github_repository_environment_deployment_policy</a>
</li>
<li>
<a href="/docs/providers/github/r/repository_environment_secret.html">github_repository_environment_secret</a>
</li>
<li>
<a href="/docs/providers/github/r/repository_environment_variable.html">github_repository_environment_variable</a>
<a href="/docs/providers/github/r/repository_environment_variable.html">github_repository_environment_variable</a>
</li>
<li>
<a href="/docs/providers/github/r/repository_file.html">github_repository_file</a>
Expand Down

0 comments on commit 64f123a

Please sign in to comment.