diff --git a/github/provider.go b/github/provider.go index 39741a2c1..76a0eea95 100644 --- a/github/provider.go +++ b/github/provider.go @@ -176,6 +176,7 @@ func Provider() *schema.Provider { "github_repository_deployment_branch_policy": resourceGithubRepositoryDeploymentBranchPolicy(), "github_repository_environment": resourceGithubRepositoryEnvironment(), "github_repository_environment_deployment_policy": resourceGithubRepositoryEnvironmentDeploymentPolicy(), + "github_repository_environment_custom_protection_rule": resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRule(), "github_repository_file": resourceGithubRepositoryFile(), "github_repository_milestone": resourceGithubRepositoryMilestone(), "github_repository_project": resourceGithubRepositoryProject(), diff --git a/github/resource_github_repository_environment_deployment_custom_protection_rule.go b/github/resource_github_repository_environment_deployment_custom_protection_rule.go new file mode 100644 index 000000000..187140f55 --- /dev/null +++ b/github/resource_github_repository_environment_deployment_custom_protection_rule.go @@ -0,0 +1,123 @@ +package github + +import ( + "context" + "log" + "net/http" + "net/url" + "strconv" + + "github.com/google/go-github/v63/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRule() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRuleCreate, + Read: resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRuleRead, + Delete: resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRuleDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + 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.", + }, + "integration_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "The ID of the custom app that will be enabled on the environment.", + }, + }, + } + +} + +func resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRuleCreate(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) + integrationID := d.Get("integration_id").(int) + escapedEnvName := url.PathEscape(envName) + + createData := github.CustomDeploymentProtectionRuleRequest{ + IntegrationID: github.Int64(int64(integrationID)), + } + + resultKey, _, err := client.Repositories.CreateCustomDeploymentProtectionRule(ctx, owner, repoName, escapedEnvName, &createData) + if err != nil { + return err + } + + d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(resultKey.GetID(), 10))) + return resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRuleRead(d, meta) +} + +func resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRuleRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + owner := meta.(*Owner).name + repoName, envName, protectionRuleIdString, err := parseThreePartID(d.Id(), "repository", "environment", "protectionRuleId") + if err != nil { + return err + } + + protectionRuleId, err := strconv.ParseInt(protectionRuleIdString, 10, 64) + if err != nil { + return err + } + protectionRule, _, err := client.Repositories.GetCustomDeploymentProtectionRule(ctx, owner, repoName, envName, protectionRuleId) + 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 custom protection for %s/%s/%s from state because it no longer exists in GitHub", + owner, repoName, envName) + d.SetId("") + return nil + } + } + return err + } + log.Printf("[INFO] Custom protection rule with node_id %s is enabled", *protectionRule.NodeID) + return nil +} + +func resourceGithubRepositoryEnvironmentDeploymentCustomProtectionRuleDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + ctx := context.Background() + + owner := meta.(*Owner).name + repoName, envName, protectionRuleIdString, err := parseThreePartID(d.Id(), "repository", "environment", "protectionRuleId") + if err != nil { + return err + } + + protectionRuleId, err := strconv.ParseInt(protectionRuleIdString, 10, 64) + if err != nil { + return err + } + + _, err = client.Repositories.DisableCustomDeploymentProtectionRule(ctx, owner, repoName, envName, protectionRuleId) + if err != nil { + return err + } + + return nil +} diff --git a/github/resource_github_repository_environment_deployment_custom_protection_rule_test.go b/github/resource_github_repository_environment_deployment_custom_protection_rule_test.go new file mode 100644 index 000000000..28962415e --- /dev/null +++ b/github/resource_github_repository_environment_deployment_custom_protection_rule_test.go @@ -0,0 +1,83 @@ +package github + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubRepositoryEnvironmentDeploymentCustomProtectionRule(t *testing.T) { + + const APP_INTEGRATION_ID = "APP_INTEGRATION_ID" + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + integration_id, exists := os.LookupEnv(APP_INTEGRATION_ID) + + t.Run("creates a repository environment with custom policy enabled", func(t *testing.T) { + if !exists { + t.Skipf("%s environment variable is missing", APP_INTEGRATION_ID) + } + config := fmt.Sprintf(` + + resource "github_repository" "test" { + name = "tf-acc-test-%s" + vulnerability_alerts = "true" + } + + resource "github_repository_environment" "test" { + repository = github_repository.test.name + environment = "environment / test" + } + + resource "github_repository_environment_custom_protection_rule" "test" { + repository = github_repository.test.name + environment = github_repository_environment.test.environment + integration_id = %s + } + + `, randomID, integration_id) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_environment_custom_protection_rule.test", "repository", + fmt.Sprintf("tf-acc-test-%s", randomID), + ), + resource.TestCheckResourceAttr( + "github_repository_environment_custom_protection_rule.test", "environment", + "environment / test", + ), + resource.TestCheckResourceAttr( + "github_repository_environment_custom_protection_rule.test", "integration_id", + integration_id, + ), + ) + + 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) + }) + + }) +} diff --git a/website/docs/r/repository_environment_custom_protection_rule.html.markdown b/website/docs/r/repository_environment_custom_protection_rule.html.markdown new file mode 100644 index 000000000..b9e2ff0e1 --- /dev/null +++ b/website/docs/r/repository_environment_custom_protection_rule.html.markdown @@ -0,0 +1,49 @@ +--- +layout: "github" +page_title: "GitHub: github_repository_environment_custom_protection_rule" +description: |- + Creates and manages environment deployment custom protection rules for GitHub repositories +--- + +# github_repository_environment_custom_protection_rule + +This resource allows you to create and manage environment deployment custom protection rules for a GitHub repository. + +## Example Usage + +```hcl + +resource "github_repository" "test" { + name = "tf-acc-test-%s" +} + +resource "github_repository_environment" "test" { + repository = github_repository.test.name + environment = "environment/test" +} + +resource "github_repository_environment_custom_protection_rule" "test" { + repository = github_repository.test.name + environment = github_repository_environment.test.environment + integration_id = 123456 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `environment` - (Required) The name of the environment. + +* `repository` - (Required) The repository of the environment. + +* `integration_id` - (Required) The ID of the custom app that will be enabled on 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 protection rule, separated by a `:` character, e.g. + +``` +$ terraform import github_repository_environment_deployment_policy.daily terraform:daily:123456 +``` diff --git a/website/github.erb b/website/github.erb index 62cac7134..fb7751d62 100644 --- a/website/github.erb +++ b/website/github.erb @@ -328,6 +328,9 @@
  • github_repository_environment
  • +
  • + github_repository_environment_custom_protection_rule +
  • github_repository_environment_deployment_policy