From 17538ae9c10232eea8add15cee796d3cb9237102 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 12 Jan 2026 23:23:49 +0200 Subject: [PATCH 1/5] Add failing test for import Signed-off-by: Timo Sand --- ...e_github_repository_environment_deployment_policy_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/github/resource_github_repository_environment_deployment_policy_test.go b/github/resource_github_repository_environment_deployment_policy_test.go index d3cc2a1592..a66b90a03d 100644 --- a/github/resource_github_repository_environment_deployment_policy_test.go +++ b/github/resource_github_repository_environment_deployment_policy_test.go @@ -72,6 +72,11 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch(t *testing.T) { Config: config, Check: check, }, + { + ResourceName: "github_repository_environment_deployment_policy.test", + ImportState: true, + ImportStateVerify: true, + }, }, }) }) From dc9ed0c7e2345ed35c4eb8e810d6e310783d9cb6 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 12 Jan 2026 23:34:36 +0200 Subject: [PATCH 2/5] Implement importer to set `environment` and `repository` values Signed-off-by: Timo Sand --- ...epository_environment_deployment_policy.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/github/resource_github_repository_environment_deployment_policy.go b/github/resource_github_repository_environment_deployment_policy.go index 0e408766c7..e3448ae8a3 100644 --- a/github/resource_github_repository_environment_deployment_policy.go +++ b/github/resource_github_repository_environment_deployment_policy.go @@ -21,7 +21,22 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicy() *schema.Resource { UpdateContext: resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate, DeleteContext: resourceGithubRepositoryEnvironmentDeploymentPolicyDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] Importing repository environment deployment policy: %s", d.Id()) + + repoName, envName, _, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") + if err != nil { + return nil, err + } + + _ = d.Set("repository", repoName) + envNameUnescaped, err := url.PathUnescape(envName) + if err != nil { + return nil, err + } + _ = d.Set("environment", envNameUnescaped) + return []*schema.ResourceData{d}, nil + }, }, Schema: map[string]*schema.Schema{ "repository": { @@ -102,6 +117,7 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyCreate(ctx context.Conte } func resourceGithubRepositoryEnvironmentDeploymentPolicyRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + log.Printf("[DEBUG] Reading repository environment deployment policy: %s", d.Id()) client := meta.(*Owner).v3client owner := meta.(*Owner).name @@ -120,6 +136,7 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyRead(ctx context.Context var ghErr *github.ErrorResponse if errors.As(err, &ghErr) { if ghErr.Response.StatusCode == http.StatusNotModified { + log.Printf("[DEBUG] API responded with StatusNotModified, not refreshing state") return nil } if ghErr.Response.StatusCode == http.StatusNotFound { From 1fb1d1b33bb22abc9b536a5006d387d316c0bf10 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Tue, 13 Jan 2026 00:08:01 +0200 Subject: [PATCH 3/5] Refactor to use `tflog` Signed-off-by: Timo Sand --- ...epository_environment_deployment_policy.go | 132 ++++++++++++++++-- 1 file changed, 120 insertions(+), 12 deletions(-) diff --git a/github/resource_github_repository_environment_deployment_policy.go b/github/resource_github_repository_environment_deployment_policy.go index e3448ae8a3..b811744fe0 100644 --- a/github/resource_github_repository_environment_deployment_policy.go +++ b/github/resource_github_repository_environment_deployment_policy.go @@ -3,13 +3,13 @@ package github import ( "context" "errors" - "log" "net/http" "net/url" "strconv" "github.com/google/go-github/v81/github" "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -22,19 +22,37 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicy() *schema.Resource { DeleteContext: resourceGithubRepositoryEnvironmentDeploymentPolicyDelete, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - log.Printf("[DEBUG] Importing repository environment deployment policy: %s", d.Id()) + tflog.Debug(ctx, "Importing repository environment deployment policy", map[string]any{ + "id": d.Id(), + }) - repoName, envName, _, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") + repoName, envName, policyId, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") if err != nil { + tflog.Error(ctx, "Failed to parse import ID", map[string]any{ + "id": d.Id(), + "error": err.Error(), + }) return nil, err } _ = d.Set("repository", repoName) envNameUnescaped, err := url.PathUnescape(envName) if err != nil { + tflog.Error(ctx, "Failed to unescape environment name", map[string]any{ + "id": d.Id(), + "environment": envName, + "error": err.Error(), + }) return nil, err } _ = d.Set("environment", envNameUnescaped) + + tflog.Info(ctx, "Imported repository environment deployment policy", map[string]any{ + "repository": repoName, + "environment": envNameUnescaped, + "policy_id": policyId, + }) + return []*schema.ResourceData{d}, nil }, }, @@ -93,31 +111,56 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyCreate(ctx context.Conte escapedEnvName := url.PathEscape(envName) var createData github.DeploymentBranchPolicyRequest + var patternType string if v, ok := d.GetOk("branch_pattern"); ok { + patternType = "branch" createData = github.DeploymentBranchPolicyRequest{ Name: github.Ptr(v.(string)), Type: github.Ptr("branch"), } } else if v, ok := d.GetOk("tag_pattern"); ok { + patternType = "tag" createData = github.DeploymentBranchPolicyRequest{ Name: github.Ptr(v.(string)), Type: github.Ptr("tag"), } - } else { - return diag.Errorf("only one of 'branch_pattern' or 'tag_pattern' must be specified") } + tflog.Debug(ctx, "Creating repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "pattern_type": patternType, + }) + resultKey, _, err := client.Repositories.CreateDeploymentBranchPolicy(ctx, owner, repoName, escapedEnvName, &createData) if err != nil { + tflog.Error(ctx, "Failed to create repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "error": err.Error(), + }) return diag.FromErr(err) } - d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(resultKey.GetID(), 10))) + policyID := resultKey.GetID() + d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(policyID, 10))) + + tflog.Info(ctx, "Created repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": policyID, + }) + return nil } func resourceGithubRepositoryEnvironmentDeploymentPolicyRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - log.Printf("[DEBUG] Reading repository environment deployment policy: %s", d.Id()) + tflog.Debug(ctx, "Reading repository environment deployment policy", map[string]any{ + "id": d.Id(), + }) client := meta.(*Owner).v3client owner := meta.(*Owner).name @@ -136,24 +179,44 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyRead(ctx context.Context var ghErr *github.ErrorResponse if errors.As(err, &ghErr) { if ghErr.Response.StatusCode == http.StatusNotModified { - log.Printf("[DEBUG] API responded with StatusNotModified, not refreshing state") + tflog.Debug(ctx, "API responded with StatusNotModified, not refreshing state") 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) + tflog.Info(ctx, "Removing branch deployment policy from state because it no longer exists in GitHub", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + }) d.SetId("") return nil } } + tflog.Error(ctx, "Failed to read repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": branchPolicyId, + "error": err.Error(), + }) return diag.FromErr(err) } - if branchPolicy.GetType() == "branch" { + patternType := branchPolicy.GetType() + if patternType == "branch" { _ = d.Set("branch_pattern", branchPolicy.GetName()) } else { _ = d.Set("tag_pattern", branchPolicy.GetName()) } + + tflog.Debug(ctx, "Successfully read repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": branchPolicyId, + "pattern_type": patternType, + }) + return nil } @@ -176,6 +239,13 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate(ctx context.Conte return diag.FromErr(err) } + tflog.Debug(ctx, "Updating repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": branchPolicyId, + }) + pattern := branchPattern if branchPattern == "" { pattern = tagPattern @@ -187,9 +257,26 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate(ctx context.Conte resultKey, _, err := client.Repositories.UpdateDeploymentBranchPolicy(ctx, owner, repoName, escapedEnvName, branchPolicyId, &updateData) if err != nil { + tflog.Error(ctx, "Failed to update repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": branchPolicyId, + "error": err.Error(), + }) return diag.FromErr(err) } - d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(resultKey.GetID(), 10))) + + policyID := resultKey.GetID() + d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(policyID, 10))) + + tflog.Info(ctx, "Updated repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": policyID, + }) + return nil } @@ -207,11 +294,32 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyDelete(ctx context.Conte return diag.FromErr(err) } + tflog.Debug(ctx, "Deleting repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": branchPolicyId, + }) + _, err = client.Repositories.DeleteDeploymentBranchPolicy(ctx, owner, repoName, envName, branchPolicyId) if err != nil { + tflog.Error(ctx, "Failed to delete repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": branchPolicyId, + "error": err.Error(), + }) return diag.FromErr(err) } + tflog.Info(ctx, "Deleted repository environment deployment policy", map[string]any{ + "owner": owner, + "repository": repoName, + "environment": envName, + "policy_id": branchPolicyId, + }) + return nil } From 5712fb4afdbce5c6956a37c7fba84c532cefd2f9 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Tue, 13 Jan 2026 21:59:34 +0200 Subject: [PATCH 4/5] Extract import to named function Signed-off-by: Timo Sand --- ...epository_environment_deployment_policy.go | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/github/resource_github_repository_environment_deployment_policy.go b/github/resource_github_repository_environment_deployment_policy.go index b811744fe0..918583e13b 100644 --- a/github/resource_github_repository_environment_deployment_policy.go +++ b/github/resource_github_repository_environment_deployment_policy.go @@ -21,40 +21,7 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicy() *schema.Resource { UpdateContext: resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate, DeleteContext: resourceGithubRepositoryEnvironmentDeploymentPolicyDelete, Importer: &schema.ResourceImporter{ - StateContext: func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - tflog.Debug(ctx, "Importing repository environment deployment policy", map[string]any{ - "id": d.Id(), - }) - - repoName, envName, policyId, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") - if err != nil { - tflog.Error(ctx, "Failed to parse import ID", map[string]any{ - "id": d.Id(), - "error": err.Error(), - }) - return nil, err - } - - _ = d.Set("repository", repoName) - envNameUnescaped, err := url.PathUnescape(envName) - if err != nil { - tflog.Error(ctx, "Failed to unescape environment name", map[string]any{ - "id": d.Id(), - "environment": envName, - "error": err.Error(), - }) - return nil, err - } - _ = d.Set("environment", envNameUnescaped) - - tflog.Info(ctx, "Imported repository environment deployment policy", map[string]any{ - "repository": repoName, - "environment": envNameUnescaped, - "policy_id": policyId, - }) - - return []*schema.ResourceData{d}, nil - }, + StateContext: resourceGithubRepositoryEnvironmentDeploymentPolicyImport, }, Schema: map[string]*schema.Schema{ "repository": { @@ -335,3 +302,44 @@ func customDeploymentPolicyDiffFunction(_ context.Context, diff *schema.Resource return nil } + +func resourceGithubRepositoryEnvironmentDeploymentPolicyImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + tflog.Debug(ctx, "Importing repository environment deployment policy", map[string]any{ + "id": d.Id(), + }) + + repoName, envName, policyId, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") + if err != nil { + tflog.Error(ctx, "Failed to parse import ID", map[string]any{ + "id": d.Id(), + "error": err.Error(), + }) + return nil, err + } + + if err := d.Set("repository", repoName); err != nil { + return nil, err + } + + envNameUnescaped, err := url.PathUnescape(envName) + if err != nil { + tflog.Error(ctx, "Failed to unescape environment name", map[string]any{ + "id": d.Id(), + "environment": envName, + "error": err.Error(), + }) + return nil, err + } + + if err := d.Set("environment", envNameUnescaped); err != nil { + return nil, err + } + + tflog.Info(ctx, "Imported repository environment deployment policy", map[string]any{ + "repository": repoName, + "environment": envNameUnescaped, + "policy_id": policyId, + }) + + return []*schema.ResourceData{d}, nil +} From e55dc6b40980eda71f199ca8585c34d5612a081e Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Wed, 14 Jan 2026 23:15:26 +0200 Subject: [PATCH 5/5] Separate import into own test Signed-off-by: Timo Sand --- ...tory_environment_deployment_policy_test.go | 74 +++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/github/resource_github_repository_environment_deployment_policy_test.go b/github/resource_github_repository_environment_deployment_policy_test.go index a66b90a03d..1ca97f9f92 100644 --- a/github/resource_github_repository_environment_deployment_policy_test.go +++ b/github/resource_github_repository_environment_deployment_policy_test.go @@ -72,11 +72,6 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch(t *testing.T) { Config: config, Check: check, }, - { - ResourceName: "github_repository_environment_deployment_policy.test", - ImportState: true, - ImportStateVerify: true, - }, }, }) }) @@ -199,6 +194,75 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranchUpdate(t *testing.T }) } +func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch_import(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%senv-deploy-%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` + + data "github_user" "current" { + username = "" + } + + resource "github_repository" "test" { + name = "%s" + ignore_vulnerability_alerts_during_read = true + } + + 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 = "main" + } + + `, repoName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_environment_deployment_policy.test", "repository", + repoName, + ), + resource.TestCheckResourceAttr( + "github_repository_environment_deployment_policy.test", "environment", + "environment/test", + ), + resource.TestCheckResourceAttr( + "github_repository_environment_deployment_policy.test", "branch_pattern", + "main", + ), + resource.TestCheckNoResourceAttr( + "github_repository_environment_deployment_policy.test", "tag_pattern", + ), + ), + }, + { + ResourceName: "github_repository_environment_deployment_policy.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccGithubRepositoryEnvironmentDeploymentPolicyTag(t *testing.T) { t.Run("creates a repository environment with tag-based deployment policy", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)