Skip to content

Commit

Permalink
Added resources for Environment + Environment Secrets (#805)
Browse files Browse the repository at this point in the history
* Added resources for Environment + Environment Secrets

* Added support for encrypted value

* Added conflicts with, fixed formatting and changes to go-github

* Fixed indentation

* Fixed indentation

* Fixed indentation

* Encrypted value needs to be in Base64 format

* fixup! add new resources to website.erb

Co-authored-by: Jeremy Udit <[email protected]>
threeseed and Jeremy Udit authored Jun 18, 2021
1 parent b9b5aa1 commit 1da31ea
Showing 9 changed files with 852 additions and 4 deletions.
2 changes: 2 additions & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
@@ -74,6 +74,7 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"github_actions_environment_secret": resourceGithubActionsEnvironmentSecret(),
"github_actions_organization_secret": resourceGithubActionsOrganizationSecret(),
"github_actions_secret": resourceGithubActionsSecret(),
"github_app_installation_repository": resourceGithubAppInstallationRepository(),
@@ -89,6 +90,7 @@ func Provider() terraform.ResourceProvider {
"github_project_column": resourceGithubProjectColumn(),
"github_repository_collaborator": resourceGithubRepositoryCollaborator(),
"github_repository_deploy_key": resourceGithubRepositoryDeployKey(),
"github_repository_environment": resourceGithubRepositoryEnvironment(),
"github_repository_file": resourceGithubRepositoryFile(),
"github_repository_milestone": resourceGithubRepositoryMilestone(),
"github_repository_project": resourceGithubRepositoryProject(),
197 changes: 197 additions & 0 deletions github/resource_github_actions_environment_secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package github

import (
"context"
"encoding/base64"
"log"
"net/http"

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

func resourceGithubActionsEnvironmentSecret() *schema.Resource {
return &schema.Resource{
Create: resourceGithubActionsEnvironmentSecretCreateOrUpdate,
Read: resourceGithubActionsEnvironmentSecretRead,
Delete: resourceGithubActionsEnvironmentSecretDelete,

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"environment": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"secret_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateSecretNameFunc,
},
"encrypted_value": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
ConflictsWith: []string{"plaintext_value"},
ValidateFunc: validation.StringIsBase64,
},
"plaintext_value": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
ConflictsWith: []string{"encrypted_value"},
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

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

repoName := d.Get("repository").(string)
envName := d.Get("environment").(string)
secretName := d.Get("secret_name").(string)
plaintextValue := d.Get("plaintext_value").(string)
var encryptedValue string

repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return err
}

keyId, publicKey, err := getEnvironmentPublicKeyDetails(repo.GetID(), envName, meta)
if err != nil {
return err
}

if encryptedText, ok := d.GetOk("encrypted_value"); ok {
encryptedValue = encryptedText.(string)
} else {
encryptedBytes, err := encryptPlaintext(plaintextValue, publicKey)
if err != nil {
return err
}
encryptedValue = base64.StdEncoding.EncodeToString(encryptedBytes)
}

// Create an EncryptedSecret and encrypt the plaintext value into it
eSecret := &github.EncryptedSecret{
Name: secretName,
KeyID: keyId,
EncryptedValue: encryptedValue,
}

_, err = client.Actions.CreateOrUpdateEnvSecret(ctx, repo.GetID(), envName, eSecret)
if err != nil {
return err
}

d.SetId(buildThreePartID(repoName, envName, secretName))
return resourceGithubActionsEnvironmentSecretRead(d, meta)
}

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

repoName, envName, secretName, err := parseThreePartID(d.Id(), "repository", "environment", "secret_name")
if err != nil {
return err
}

repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return err
}

secret, _, err := client.Actions.GetEnvSecret(ctx, repo.GetID(), envName, secretName)
if err != nil {
if ghErr, ok := err.(*github.ErrorResponse); ok {
if ghErr.Response.StatusCode == http.StatusNotFound {
log.Printf("[WARN] Removing environment secret %s from state because it no longer exists in GitHub",
d.Id())
d.SetId("")
return nil
}
}
return err
}

d.Set("encrypted_value", d.Get("encrypted_value"))
d.Set("plaintext_value", d.Get("plaintext_value"))
d.Set("created_at", secret.CreatedAt.String())

// This is a drift detection mechanism based on timestamps.
//
// If we do not currently store the "updated_at" field, it means we've only
// just created the resource and the value is most likely what we want it to
// be.
//
// If the resource is changed externally in the meantime then reading back
// the last update timestamp will return a result different than the
// timestamp we've persisted in the state. In that case, we can no longer
// trust that the value (which we don't see) is equal to what we've declared
// previously.
//
// The only solution to enforce consistency between is to mark the resource
// as deleted (unset the ID) in order to fix potential drift by recreating
// the resource.
if updatedAt, ok := d.GetOk("updated_at"); ok && updatedAt != secret.UpdatedAt.String() {
log.Printf("[WARN] The environment secret %s has been externally updated in GitHub", d.Id())
d.SetId("")
} else if !ok {
d.Set("updated_at", secret.UpdatedAt.String())
}

return nil
}

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

repoName, envName, secretName, err := parseThreePartID(d.Id(), "repository", "environment", "secret_name")
if err != nil {
return err
}
repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return err
}
log.Printf("[DEBUG] Deleting environment secret: %s", d.Id())
_, err = client.Actions.DeleteEnvSecret(ctx, repo.GetID(), envName, secretName)

return err
}

func getEnvironmentPublicKeyDetails(repoID int64, envName string, meta interface{}) (keyId, pkValue string, err error) {
client := meta.(*Owner).v3client
ctx := context.Background()

publicKey, _, err := client.Actions.GetEnvPublicKey(ctx, int(repoID), envName)
if err != nil {
return keyId, pkValue, err
}

return publicKey.GetKeyID(), publicKey.GetKey(), err
}
171 changes: 171 additions & 0 deletions github/resource_github_actions_environment_secret_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package github

import (
"fmt"
"strings"
"testing"

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

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

func TestAccGithubActionsEnvironmentSecret(t *testing.T) {

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

t.Run("creates and updates secrets without error", func(t *testing.T) {

secretValue := "super_secret_value"
updatedSecretValue := "updated_super_secret_value"

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
}
resource "github_repository_environment" "test" {
repository = github_repository.test.name
environment = "test_environment_name"
}
resource "github_actions_environment_secret" "plaintext_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_plaintext_secret_name"
plaintext_value = "%s"
}
resource "github_actions_environment_secret" "encrypted_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_encrypted_secret_name"
encrypted_value = "%s"
}
`, randomID, secretValue, secretValue)

checks := map[string]resource.TestCheckFunc{
"before": resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret", "plaintext_value",
secretValue,
),
resource.TestCheckResourceAttr(
"github_actions_environment_secret.encrypted_secret", "encrypted_value",
secretValue,
),
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "created_at",
),
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "updated_at",
),
),
"after": resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret", "plaintext_value",
updatedSecretValue,
),
resource.TestCheckResourceAttr(
"github_actions_environment_secret.encrypted_secret", "encrypted_value",
updatedSecretValue,
),
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "created_at",
),
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "updated_at",
),
),
}

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: checks["before"],
},
{
Config: strings.Replace(config,
secretValue,
updatedSecretValue, 2),
Check: checks["after"],
},
},
})
}

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)
})

})

t.Run("deletes secrets without error", func(t *testing.T) {

secretValue := "super_secret_value"

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
}
resource "github_repository_environment" "test" {
repository = github_repository.test.name
environment = "test_environment_name"
}
resource "github_actions_environment_secret" "plaintext_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_plaintext_secret_name"
plaintext_value = "%s"
}
resource "github_actions_environment_secret" "encrypted_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_encrypted_secret_name"
encrypted_value = "%s"
}
`, randomID, secretValue, secretValue)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Destroy: true,
},
},
})
}

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)
})

})

}
254 changes: 254 additions & 0 deletions github/resource_github_repository_environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package github

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

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

func resourceGithubRepositoryEnvironment() *schema.Resource {
return &schema.Resource{
Create: resourceGithubRepositoryEnvironmentCreate,
Read: resourceGithubRepositoryEnvironmentRead,
Update: resourceGithubRepositoryEnvironmentUpdate,
Delete: resourceGithubRepositoryEnvironmentDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"environment": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"wait_timer": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 43200),
},
"reviewers": {
Type: schema.TypeList,
Optional: true,
MaxItems: 6,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"teams": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeInt},
},
"users": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeInt},
},
},
},
},
"deployment_branch_policy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protected_branches": {
Type: schema.TypeBool,
Required: true,
},
"custom_branch_policies": {
Type: schema.TypeBool,
Required: true,
},
},
},
},
},
}
}

func resourceGithubRepositoryEnvironmentCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
envName := d.Get("environment").(string)
updateData := createUpdateEnvironmentData(d, meta)

ctx := context.Background()

log.Printf("[DEBUG] Creating repository environment: %s/%s/%s", owner, repoName, envName)
_, _, err := client.Repositories.CreateUpdateEnvironment(ctx, owner, repoName, envName, &updateData)

if err != nil {
return err
}

d.SetId(buildTwoPartID(repoName, envName))

return resourceGithubRepositoryEnvironmentRead(d, meta)
}

func resourceGithubRepositoryEnvironmentRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

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

ctx := context.WithValue(context.Background(), ctxId, d.Id())

log.Printf("[DEBUG] Reading repository environment: %s (%s/%s/%s)", d.Id(), owner, repoName, envName)
env, _, err := client.Repositories.GetEnvironment(ctx, owner, repoName, envName)
if err != nil {
if ghErr, ok := err.(*github.ErrorResponse); ok {
if ghErr.Response.StatusCode == http.StatusNotFound {
log.Printf("[WARN] Removing repository environment %s from state because it no longer exists in GitHub",
d.Id())
d.SetId("")
return nil
}
}
}

d.Set("repository", repoName)
d.Set("environment", envName)

for _, pr := range env.ProtectionRules {
switch *pr.Type {
case "wait_timer":
d.Set("wait_timer", pr.WaitTimer)

case "required_reviewers":
teams := make([]int64, 0)
users := make([]int64, 0)

for _, r := range pr.Reviewers {
switch *r.Type {
case "Team":
teams = append(teams, *r.Reviewer.(*github.Team).ID)
case "User":
users = append(users, *r.Reviewer.(*github.User).ID)
}
}
d.Set("reviewers", []interface{}{
map[string]interface{}{
"teams": teams,
"users": users,
},
})
}
}

if env.DeploymentBranchPolicy != nil {
d.Set("deployment_branch_policy", []interface{}{
map[string]interface{}{
"protected_branches": env.DeploymentBranchPolicy.ProtectedBranches,
"custom_branch_policies": env.DeploymentBranchPolicy.CustomBranchPolicies,
},
})
}

return nil
}

func resourceGithubRepositoryEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
envName := d.Get("environment").(string)
updateData := createUpdateEnvironmentData(d, meta)

ctx := context.Background()

log.Printf("[DEBUG] Updating repository environment: %s/%s/%s", owner, repoName, envName)
resultKey, _, err := client.Repositories.CreateUpdateEnvironment(ctx, owner, repoName, envName, &updateData)

if err != nil {
return err
}

d.SetId(buildTwoPartID(repoName, strconv.FormatInt(resultKey.GetID(), 10)))

return resourceGithubRepositoryEnvironmentRead(d, meta)
}

func resourceGithubRepositoryEnvironmentDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

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

ctx := context.WithValue(context.Background(), ctxId, d.Id())

log.Printf("[DEBUG] Deleting repository environment: %s/%s/%s", owner, repoName, envName)
_, err = client.Repositories.DeleteEnvironment(ctx, owner, repoName, envName)
return err
}

func createUpdateEnvironmentData(d *schema.ResourceData, meta interface{}) github.CreateUpdateEnvironment {
data := github.CreateUpdateEnvironment{}

if v, ok := d.GetOk("wait_timer"); ok {
data.WaitTimer = github.Int(v.(int))
}

if v, ok := d.GetOk("reviewers"); ok {
envReviewers := make([]*github.EnvReviewers, 0)

for _, team := range expandReviewers(v, "teams") {
envReviewers = append(envReviewers, &github.EnvReviewers{
Type: github.String("Team"),
ID: github.Int64(team),
})
}

for _, user := range expandReviewers(v, "users") {
envReviewers = append(envReviewers, &github.EnvReviewers{
Type: github.String("User"),
ID: github.Int64(user),
})
}

data.Reviewers = envReviewers
}

if v, ok := d.GetOk("deployment_branch_policy"); ok {
policy := v.([]interface{})[0].(map[string]interface{})
data.DeploymentBranchPolicy = &github.BranchPolicy{
ProtectedBranches: github.Bool(policy["protected_branches"].(bool)),
CustomBranchPolicies: github.Bool(policy["custom_branch_policies"].(bool)),
}
}

return data
}

func expandReviewers(v interface{}, target string) []int64 {
res := make([]int64, 0)
m := v.([]interface{})[0]
if m != nil {
if v, ok := m.(map[string]interface{})[target]; ok {
vL := v.(*schema.Set).List()
for _, v := range vL {
res = append(res, int64(v.(int)))
}
}
}
return res
}
75 changes: 75 additions & 0 deletions github/resource_github_repository_environment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package github

import (
"fmt"
"testing"

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

func TestAccGithubRepositoryEnvironment(t *testing.T) {

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

t.Run("creates a repository environment", 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 = "test_environment_name"
wait_timer = 10000
reviewers {
users = [data.github_user.current.id]
}
deployment_branch_policy {
protected_branches = true
custom_branch_policies = false
}
}
`, randomID)

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_repository_environment.test", "environment",
"test_environment_name",
),
)

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)
})

})
}
82 changes: 82 additions & 0 deletions website/docs/r/actions_environment_secret.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
layout: "github"
page_title: "GitHub: github_actions_environment_secret"
description: |-
Creates and manages an Action Secret within a GitHub repository environment
---

# github_actions_environment_secret

This resource allows you to create and manage GitHub Actions secrets within your GitHub repository environments.
You must have write access to a repository to use this resource.

Secret values are encrypted using the [Go '/crypto/box' module](https://godoc.org/golang.org/x/crypto/nacl/box) which is
interoperable with [libsodium](https://libsodium.gitbook.io/doc/). Libsodium is used by GitHub to decrypt secret values.

For the purposes of security, the contents of the `plaintext_value` field have been marked as `sensitive` to Terraform,
but it is important to note that **this does not hide it from state files**. You should treat state as sensitive always.
It is also advised that you do not store plaintext values in your code but rather populate the `encrypted_value`
using fields from a resource, data source or variable as, while encrypted in state, these will be easily accessible
in your code. See below for an example of this abstraction.

## Example Usage

```hcl
resource "github_actions_environment_secret" "example_secret" {
environment = "example_environment"
secret_name = "example_secret_name"
plaintext_value = var.some_secret_string
}
resource "github_actions_environment_secret" "example_secret" {
environment = "example_environment"
secret_name = "example_secret_name"
encrypted_value = var.some_encrypted_secret_string
}
```

```hcl
data "github_repository" "repo" {
full_name = "my-org/repo"
}
resource "github_repository_environment" "repo_environment" {
repository = data.github_repository.repo
environment = "example_environment"
}
resource "github_actions_environment_secret" "test_secret" {
repository = data.github_repository.repo
environment = github_repository_environment.repo_environment.environment
secret_name = "test_secret_name"
plaintext_value = "%s"
}
```

## Argument Reference

The following arguments are supported:


* `repository` - (Required) Name of the repository.
* `environment` - (Required) Name of the environment.
* `secret_name` - (Required) Name of the secret.
* `encrypted_value` - (Optional) Encrypted value of the secret using the Github public key in Base64 format.
* `plaintext_value` - (Optional) Plaintext value of the secret to be encrypted.

## Attributes Reference

* `created_at` - Date of actions_environment_secret creation.
* `updated_at` - Date of actions_environment_secret update.

## Import

This resource can be imported using an ID made up of the secret name:

```
$ terraform import github_actions_environment_secret.test_secret test_secret_name
```

NOTE: the implementation is limited in that it won't fetch the value of the
`plaintext_value` field when importing. You may need to ignore changes for the
`plaintext_value` as a workaround.
61 changes: 61 additions & 0 deletions website/docs/r/repository_environment.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
layout: "github"
page_title: "GitHub: github_repository_environment"
description: |-
Creates and manages environments for GitHub repositories
---

# github_repository_environment

This resource allows you to create and manage environments for a GitHub repository.

## Example Usage

```hcl
data "github_user" "current" {
username = ""
}
resource "github_repository" "example" {
name = "example"
description = "My awesome codebase"
}
resource "github_repository_environment" "example" {
name = "A Repository Project"
repository = github_repository.example.name
reviewers {
users = [data.github_user.current.id]
}
deployment_branch_policy {
protected_branches = true
custom_branch_policies = false
}
}
```

## Argument Reference

The following arguments are supported:

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

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

* `wait_timer` - (Optional) Amount of time to delay a job after the job is initially triggered.

### Reviewers

The `reviewers` block supports the following:

* `teams` - (Optional) Up to 6 IDs for teams who may review jobs that reference the environment. Reviewers must have at least read access to the repository. Only one of the required reviewers needs to approve the job for it to proceed.

* `users` - (Optional) Up to 6 IDs for users who may review jobs that reference the environment. Reviewers must have at least read access to the repository. Only one of the required reviewers needs to approve the job for it to proceed.

#### Deployment Branch Policy ####

The `deployment_branch_policy` block supports the following:

* `protected_branches` - (Required) Whether only branches with branch protection rules can deploy to this environment.

* `custom_branch_policies` - (Required) Whether only branches that match the specified name patterns can deploy to this environment.
6 changes: 6 additions & 0 deletions website/github.erb
Original file line number Diff line number Diff line change
@@ -109,6 +109,12 @@
<li>
<a href="/docs/providers/github/r/repository_deploy_key.html">github_repository_deploy_key</a>
</li>
<li>
<a href="/docs/providers/github/r/repository_environment.html">github_repository_environment</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_file.html">github_repository_file</a>
</li>

0 comments on commit 1da31ea

Please sign in to comment.