Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
56 changes: 56 additions & 0 deletions github/resource_github_branch_protection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,62 @@ 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)
config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "%s"
auto_init = true
}

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

configArchived := fmt.Sprintf(`
Comment thread
hanyouqing marked this conversation as resolved.
Outdated
resource "github_repository" "test" {
name = "%s"
auto_init = true
archived = true
}

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

resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnauthenticated(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
Comment thread
hanyouqing marked this conversation as resolved.
Outdated
resource.TestCheckResourceAttr("github_branch_protection.test", "pattern", "main"),
),
},
{
Config: configArchived,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("github_repository.test", "archived", "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
Comment thread
hanyouqing marked this conversation as resolved.
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