Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions github/resource_github_branch_protection.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ func resourceGithubBranchProtection() *schema.Resource {
PROTECTION_REQUIRED_STATUS_CHECK_CONTEXTS: {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Deprecated: "GitHub is deprecating the use of `contexts`. Use a `checks` array instead.",
Description: "The list of status checks to require in order to merge into this branch. No status checks are required by default.",
Elem: &schema.Schema{Type: schema.TypeString},
},
Expand Down Expand Up @@ -293,6 +295,12 @@ func resourceGithubBranchProtectionRead(d *schema.ResourceData, meta any) error
}
protection := query.Node.Node

if protection.Repository.IsArchived {
log.Printf("[INFO] Removing branch protection (%s) from state because the repository (%s) is archived", d.Id(), protection.Repository.Name)
d.SetId("")
return nil
}

err = d.Set(PROTECTION_PATTERN, protection.Pattern)
if err != nil {
log.Printf("[DEBUG] Problem setting '%s' in %s %s branch protection (%s)", PROTECTION_PATTERN, protection.Repository.Name, protection.Pattern, d.Id())
Expand Down
51 changes: 51 additions & 0 deletions github/resource_github_branch_protection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (

"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
)

func TestAccGithubBranchProtectionV4(t *testing.T) {
Expand Down Expand Up @@ -164,6 +167,54 @@ func TestAccGithubBranchProtectionV4(t *testing.T) {
})
})

t.Run("removes from state when repository is archived", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
testRepoName := fmt.Sprintf("%sbranch-protection-%s", testResourcePrefix, randomID)

configTemplate := `
resource "github_repository" "test" {
name = "%s"
auto_init = true
archived = %t
}

resource "github_branch_protection" "test" {
repository_id = github_repository.test.node_id
pattern = "main"
}
`

config := fmt.Sprintf(configTemplate, testRepoName, false)
configArchived := fmt.Sprintf(configTemplate, testRepoName, true)

resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnauthenticated(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: config,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("github_branch_protection.test", tfjsonpath.New("pattern"), knownvalue.StringExact("main")),
},
},
{
Config: configArchived,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("archived"), knownvalue.Bool(true)),
},
},
{
Config: configArchived,
ResourceName: "github_branch_protection.test",
ImportState: true,
ImportStateVerify: false, // Should fail to import because it's removed from state
ExpectError: regexp.MustCompile(`could not find a branch protection rule`),
ImportStateIdFunc: importBranchProtectionByRepoID("github_repository.test", "main"),
},
},
})
})

t.Run("configures required status checks", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
testRepoName := fmt.Sprintf("%sbranch-protection-%s", testResourcePrefix, randomID)
Expand Down
20 changes: 20 additions & 0 deletions github/resource_github_branch_protection_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,26 @@ func resourceGithubBranchProtectionV3Read(d *schema.ResourceData, meta any) erro
orgName := meta.(*Owner).name

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

repo, _, err := client.Repositories.Get(ctx, orgName, repoName)
if err != nil {
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) {
if ghErr.Response.StatusCode == http.StatusNotFound {
log.Printf("[INFO] Removing branch protection %s/%s (%s) from state because the repository no longer exists",
orgName, repoName, branch)
d.SetId("")
return nil
}
}
return err
}
if repo.GetArchived() {
log.Printf("[INFO] Removing branch protection %s/%s (%s) from state because the repository is archived", orgName, repoName, branch)
d.SetId("")
return nil
}

if !d.IsNewResource() {
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
}
Expand Down
94 changes: 94 additions & 0 deletions github/resource_github_branch_protection_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,100 @@ func TestAccGithubBranchProtectionV3(t *testing.T) {
})
})

t.Run("removes from state when repository is archived", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
testRepoName := fmt.Sprintf("%sbranch-protection-%s", testResourcePrefix, randomID)
config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "%s"
auto_init = true
}

resource "github_branch_protection_v3" "test" {
repository = github_repository.test.name
branch = "main"
}
`, testRepoName)

configArchived := fmt.Sprintf(`
resource "github_repository" "test" {
name = "%s"
auto_init = true
archived = true
}

resource "github_branch_protection_v3" "test" {
repository = github_repository.test.name
branch = "main"
}
`, testRepoName)

resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessHasOrgs(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("github_branch_protection_v3.test", "branch", "main"),
),
},
{
Config: configArchived,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("github_repository.test", "archived", "true"),
),
},
{
Config: configArchived,
ResourceName: "github_branch_protection_v3.test",
ImportState: true,
ImportStateVerify: false, // Should fail to import because it's removed from state
ExpectError: nil, // Terraform usually succeeds with an empty ID if we return nil in Read
ImportStateIdFunc: func(s *terraform.State) (string, error) {
return fmt.Sprintf("%s:main", testRepoName), nil
},
},
},
})
})

t.Run("fallbacks from checks to contexts", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
testRepoName := fmt.Sprintf("%sbranch-protection-%s", testResourcePrefix, randomID)
config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "%s"
auto_init = true
}

resource "github_branch_protection_v3" "test" {
repository = github_repository.test.name
branch = "main"

required_status_checks {
strict = true
checks = ["ci/test"]
}
}
`, testRepoName)

resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessHasOrgs(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("github_branch_protection_v3.test", "required_status_checks.0.checks.#", "1"),
resource.TestCheckResourceAttr("github_branch_protection_v3.test", "required_status_checks.0.contexts.#", "1"),
resource.TestCheckTypeSetElemAttr("github_branch_protection_v3.test", "required_status_checks.0.contexts.*", "ci/test"),
),
},
},
})
})

t.Run("configures required status checks", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
testRepoName := fmt.Sprintf("%sbranch-protection-%s", testResourcePrefix, randomID)
Expand Down
29 changes: 20 additions & 9 deletions github/resource_github_branch_protection_v3_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,29 @@ func flattenAndSetRequiredStatusChecks(d *schema.ResourceData, protection *githu

// TODO: Remove once contexts is fully deprecated.
// Flatten contexts
for _, c := range *rsc.Contexts {
// Parse into contexts
contexts = append(contexts, c)
if rsc.Contexts != nil {
for _, c := range *rsc.Contexts {
// Parse into contexts
contexts = append(contexts, c)
}
}

// Fallback to populating contexts from checks if it's empty (e.g. archived repo)
if len(contexts) == 0 && rsc.Checks != nil {
for _, chk := range *rsc.Checks {
contexts = append(contexts, chk.Context)
}
}

// Flatten checks
for _, chk := range *rsc.Checks {
// Parse into checks
if chk.AppID != nil {
checks = append(checks, fmt.Sprintf("%s:%d", chk.Context, *chk.AppID))
} else {
checks = append(checks, chk.Context)
if rsc.Checks != nil {
for _, chk := range *rsc.Checks {
// Parse into checks
if chk.AppID != nil {
checks = append(checks, fmt.Sprintf("%s:%d", chk.Context, *chk.AppID))
} else {
checks = append(checks, chk.Context)
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions github/util_v4_branch_protection.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ type PushActorTypes struct {

type BranchProtectionRule struct {
Repository struct {
ID githubv4.String
Name githubv4.String
ID githubv4.String
Name githubv4.String
IsArchived githubv4.Boolean
}
PushAllowances struct {
Nodes []PushActorTypes
Expand Down