Skip to content

fix(OSAC-3445): guard sub-resources for archived repos in common_repository module - #165

Open
amej wants to merge 1 commit into
osac-project:mainfrom
amej:fix/OSAC-3445-archived-guards
Open

fix(OSAC-3445): guard sub-resources for archived repos in common_repository module#165
amej wants to merge 1 commit into
osac-project:mainfrom
amej:fix/OSAC-3445-archived-guards

Conversation

@amej

@amej amej commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds var.archived guards to modules/common_repository/main.tf for pages, github_issue_label, github_branch_protection, github_repository_ruleset.status_checks, and github_repository_environment — GitHub returns 409 on writes to these sub-resources for archived repos.
  • github_repository.repo already set archived = var.archived; the module never skipped the dependent sub-resources, which is the root cause of the tofu apply GitHub Action failures below.

Related PRs whose tofu apply failed because of this gap

PR Jira Description
#158 OSAC-3534 Set archived=true for repos merged into osac mono-repo (original introduction of the gap)
#161 OSAC-3534 Revert of the revert — re-applied archived=true without the module fix
#163 OSAC-3445 Archived host-management-openstack — hit the same 409 failures

(PR #159, the interim revert of #158, is not affected since it undid the archival rather than triggering the bug.)

What gets unblocked

Once merged, the pending tofu apply drift for the repos archived in #158/#161/#163 (fulfillment-service, osac-operator, osac-aap, osac-installer, bare-metal-fulfillment-operator, osac-csi-driver, host-management-openstack) will apply cleanly instead of failing with 409 errors on branch protection, rulesets, labels, pages, and environments.

Test plan

  • tofu init -backend=false && tofu validate — passes (only pre-existing deprecation warnings)
  • CodeRabbit CLI review (--agent, committed vs upstream/main) — 0 findings
  • tofu apply succeeds in CI for the previously-failing archived repos

Assisted-by: Claude Code (claude-sonnet-5)

Summary by CodeRabbit

  • Bug Fixes
    • Archived repositories are now excluded from unsupported GitHub configuration updates, preventing 409 errors.
    • Active repositories continue to receive GitHub Pages, labels, branch protection, status-check rulesets, and environment settings as before.

…sitory module

GitHub rejects writes (409) to pages, labels, branch protection, rulesets,
and environments on archived repos. archived=true was already threaded
through repositories.tf but the module never skipped these sub-resources,
which broke tofu apply once any repo was archived.

Assisted-by: Claude Code (claude-sonnet-5)

Signed-off-by: Ameya Sathe <asathe@redhat.com>

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Archived repositories now skip GitHub Pages, issue labels, branch protection, status-check rulesets, and environments. Non-archived repository behavior remains unchanged.

Changes

Archived repository handling

Layer / File(s) Summary
Skip unsupported resources for archived repositories
modules/common_repository/main.tf
GitHub Pages, issue labels, and environments are no longer created for archived repositories.
Disable protection resources for archived repositories
modules/common_repository/main.tf
Branch protection and status-check rulesets are disabled for archived repositories.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: eliorerz, omer-vishlitzky, rccrdpccl

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes guarding dependent sub-resources for archived repositories.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No-Hardcoded-Secrets ✅ Passed The PR changes only modules/common_repository/main.tf; added lines contain Terraform guards/comments and no credential assignments, embedded-credential URLs, private-key markers, or long base64/hex...
No-Weak-Crypto ✅ Passed The PR changes only Terraform resource guards; the added lines contain no MD5, SHA1, DES, RC4, Blowfish, ECB, custom crypto, or secret comparison.
No-Injection-Vectors ✅ Passed The PR adds only Terraform comments and var.archived-based resource guards; no SQL, shell, eval/exec, pickle, unsafe YAML, os.system, or dangerouslySetInnerHTML patterns were found.
Container-Privileges ✅ Passed The PR changes only Terraform GitHub resources. No container/Kubernetes manifests or privileged settings such as hostPID, hostNetwork, SYS_ADMIN, or allowPrivilegeEscalation were found.
No-Sensitive-Data-In-Logs ✅ Passed The PR changes only Terraform guards and comments; added lines contain no logging or sensitive data, and no relevant logging constructs were found in Terraform files.
Ai-Attribution ✅ Passed AI use is disclosed, and the PR commit contains Assisted-by: Claude Code (claude-sonnet-5); no AI-associated Co-Authored-By trailer was found.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/common_repository/main.tf`:
- Around line 55-56: Update modules/common_repository/main.tf at lines 55-56,
70-72, 108-108, and 155-156 so archived repositories do not change resource
instance presence via empty for_each or count = 0. Detach these resources from
Terraform or manage them outside Terraform while preserving existing labels,
environments, branch protection, and rulesets; do not rename resources or create
a destroyed/kept state.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: db5e4e54-869d-47c9-861e-9e110d43385a

📥 Commits

Reviewing files that changed from the base of the PR and between 2d0fea9 and b077704.

📒 Files selected for processing (1)
  • modules/common_repository/main.tf

Comment on lines +55 to +56
# Skip labels for archived repos (GitHub rejects writes with 409)
for_each = var.archived ? {} : {

@coderabbitai coderabbitai Bot Aug 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file stats =="
wc -l modules/common_repository/main.tf

echo "== relevant sections =="
sed -n '1,220p' modules/common_repository/main.tf | cat -n

echo "== lifecycle/search relevant resources =="
rg -n "lifecycle|detach|destroy|for_each|count = var|github_repository|github_branch|github_ruleset|github_repository_environment|github" modules/common_repository/main.tf modules -g '*.tf'

Repository: osac-project/github-config

Length of output: 13536


🌐 Web query:

Terraform empty for_each removes instances destroys state resource multiple labels dynamic for_each empty branch protection count zero destructive

💡 Result:

In Terraform, both count and for_each serve as meta-arguments to manage multiple resource instances, and both handle empty inputs by removing all previously managed instances of that resource [1][2]. When you provide an empty map or set to for_each, or a count of zero, Terraform recognizes that no instances should exist [1][2]. If those instances were previously managed in the state, Terraform will plan to destroy them [3][4]. Key technical behaviors: - For_each: Terraform tracks resources by their map key or set member [5]. When a key is removed from the input collection, Terraform marks the associated resource instance for destruction [4][6]. If the input becomes an empty map {}, all associated instances in the state are destroyed [1][4]. - Count: Terraform tracks resources by their integer index [4]. Setting count = 0 instructs Terraform to destroy all instances of that resource [2][7]. - Dynamic Blocks: When using dynamic blocks, an empty collection in the for_each argument results in the block not being rendered at all [1][8]. This is a common pattern for conditionally creating or omitting configuration blocks [8]. - Destructive Actions: Both methods perform standard destruction of infrastructure objects in the state [1][2]. Neither inherently ignores existing state unless specifically handled; they proactively reconcile the state to match the configuration (which has zero instances) [3]. - Prevent Destroy: The lifecycle argument prevent_destroy can be used to block the destruction of resources, even if you remove them from your configuration (e.g., by setting for_each to an empty map) [9]. If Terraform attempts to destroy a resource with this flag, it will return an error and halt the plan/apply process [9]. If you are experiencing unexpected destruction, verify that your for_each or count expression is resolving to an empty value (e.g., {} or 0) as intended, and check for any lifecycle rules or external dependencies that might be affected [7][8].

Citations:


Do not remove archived resource instances from configuration.

The archive guards set github_issue_label.repo_labels and github_repository_environment.env to an empty for_each, and github_branch_protection.repo_protection and github_repository_ruleset.status_checks to count = 0. Terraform treats these as destroyed instances, so applying can issue writes to delete labels, branch protection, rulesets, or environments on archived repositories instead of preserving them. Detach these resources or manage them outside Terraform for archived repos; do not create a separate destroyed/kept state with a name change.

📍 Affects 1 file
  • modules/common_repository/main.tf#L55-L56 (this comment)
  • modules/common_repository/main.tf#L70-L72
  • modules/common_repository/main.tf#L108-L108
  • modules/common_repository/main.tf#L155-L156
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/common_repository/main.tf` around lines 55 - 56, Update
modules/common_repository/main.tf at lines 55-56, 70-72, 108-108, and 155-156 so
archived repositories do not change resource instance presence via empty
for_each or count = 0. Detach these resources from Terraform or manage them
outside Terraform while preserving existing labels, environments, branch
protection, and rulesets; do not rename resources or create a destroyed/kept
state.

@amej amej Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eliorerz : I am unsure about the right approach here.

Quoting chai-bot here;

PR #165 Review — Looks Good ✅

PR #165 implements all 5 changes correctly in modules/common_repository/main.tf:

  • ✅ pages dynamic block — guarded with var.archived ? [] : var.pages[*]
  • ✅ github_issue_label — guarded with var.archived ? {} : { ... }
  • ✅ github_branch_protection — guarded with var.archived ? 0 in the count chain
  • ✅ github_repository_ruleset — same pattern in the count chain
  • ✅ github_repository_environment — guarded with var.archived ? {} : { ... }

Only modules/common_repository/main.tf was modified (10 additions, 6 deletions). CI is green — pre-commit passed.

About CodeRabbit's "Changes Requested"

CodeRabbit flagged a theoretical concern: setting count = 0 / for_each = {} causes Terraform to plan destruction of existing sub-resources (labels, branch protection, rulesets, environments) on archived repos, potentially issuing DELETE API calls to GitHub.

This concern is technically valid but practically a non-issue for two reasons:

  1. The sub-resources are meaningless on archived repos. Labels, branch protection, rulesets, and environments serve no purpose on a read-only archived repository. Removing them from Terraform state is the correct behavior — you don't want Terraform managing resources that GitHub won't let it update.

  2. GitHub typically allows DELETEs on archived repos. The 409 errors you're seeing are on PUT/POST (create/update) operations. DELETE operations generally succeed on archived repos. In the unlikely case a delete fails, the worst outcome is a harmless error on the first apply, resolvable with a one-time tofu state rm for the affected resource.

CodeRabbit's suggested alternative — "detach these resources from Terraform or manage them outside Terraform" — would require running tofu state rm for every sub-resource of every archived repo before applying. That's a complex manual operation that's more error-prone than the clean approach in your PR.

Recommendation: The PR is ready to merge. You can safely dismiss CodeRabbit's comment. If the first apply after merge encounters any delete failures on already-archived repos (unlikely), run:tofu state rm 'module.repo_host_management_openstack.github_branch_protection.repo_protection[0]'
for any such resource to clean up the orphaned state entry.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🤖 web_search executed:

GitHub REST API archived repository delete issue label branch protection repository ruleset environment collaborator 409 documentation

Length of output: 11125


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Terraform/OpenTofu provider lock or required provider definitions =="
rg -n -C 3 'required_providers|integrations/github|github.*=' -g '*.tf' -g '*.lock.hcl' .

echo "== Archived-resource declarations and all unguarded GitHub resources =="
rg -n -C 5 '^resource "github_|var\.archived|for_each =|count =' modules/common_repository/main.tf

echo "== Provider source references, if vendored or pinned locally =="
fd -HI '.*github.*' .terraform . 2>/dev/null | head -200 || true

Length of output: 7871


🤖 web_search executed:

site:github.com/integrations/terraform-provider-github v6.12.1 resourceGithubIssueLabel Delete github_branch_protection github_repository_ruleset github_repository_environment

Length of output: 14015


🤖 web_search executed:

site:docs.github.com archived repositories read-only REST API delete repository labels branch protection rulesets environments collaborators

Length of output: 14237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="integrations/terraform-provider-github"
ref="v6.12.1"

for file in \
  github/resource_github_issue_label.go \
  github/resource_github_branch_protection.go \
  github/resource_github_repository_ruleset.go \
  github/resource_github_repository_environment.go \
  github/resource_github_repository_collaborators.go
do
  echo "== ${file} =="
  gh api "repos/${repo}/contents/${file}?ref=${ref}" \
    --jq '.content' | base64 --decode \
    | rg -n -C 8 'func .*Delete|Delete\(|archived|Archived|Remove|destroy' \
    || true
done

Length of output: 7835


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="integrations/terraform-provider-github"
ref="v6.12.1"

for file in \
  github/resource_github_branch_protection.go \
  github/resource_github_repository_environment.go \
  github/resource_github_issue_label.go \
  github/resource_github_repository_ruleset.go \
  github/utils.go \
  github/provider.go
do
  echo "== ${file} =="
  gh api "repos/${repo}/contents/${file}?ref=${ref}" \
    --jq '.content' | base64 --decode \
    | rg -n -C 18 'func resourceGithubBranchProtectionDelete|func resourceGithubRepositoryEnvironmentDelete|func handleArchivedRepoDelete' \
    || true
done

Length of output: 3827


@amej, I would not dismiss the comment as written. I would narrow it.

The quoted review is correct for github_issue_label. Provider version 6.12.1 explicitly skips label deletion for archived repositories and removes the label from state. The provider documentation states this behavior.

The quoted review is not correct for all guarded resources:

  • github_branch_protection runs the GraphQL deleteBranchProtectionRule mutation directly. It has no archived-repository error handling.
  • github_repository_environment runs DeleteEnvironment directly. It only handles 404 and 304 responses. It has no archived-repository error handling.
  • Setting count = 0 or for_each = {} schedules these delete writes for already-managed instances.

Therefore, the current change can still fail when it archives a repository that has managed branch protection or environments. It can also delete those settings if GitHub accepts the requests.

github_repository_ruleset uses the provider's archive-delete handler. It is closer to the label case. However, it still removes the resource from state. That is acceptable only if this PR intentionally stops managing rulesets after archival.

The main decision is lifecycle policy:

  1. If archival must preserve settings for possible unarchival, do not remove the instances from configuration or state as a side effect of var.archived.
  2. If archival must intentionally discard managed settings, document that behavior and verify a plan for each archived repository before apply.
  3. Do not rely on undocumented DELETE behavior for branch protection and environments.

I recommend retaining the finding for github_branch_protection and github_repository_environment, and withdrawing the label portion. The current PR should not merge solely on the assumption that DELETE requests are harmless.

You are interacting with an AI system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant