Skip to content

[BUG]: Getting an error when trying to delete a repo #3331

@aalloul

Description

@aalloul

Expected Behavior

I commented out the whole the content of the terraform file and expected the repo to be completely deleted. I also tried terraform destroy and got the same error

Actual Behavior

I got the error

│ Error: error deleting GitHub branch reference exnihilo-studio/test_terraform_repo (refs/heads/main): DELETE https://api.github.com/repos/exnihilo-studio/test_terraform_repo/git/refs/heads/main: 422 Cannot delete the default branch []

Terraform Version

Terraform v1.14.8
on darwin_arm64
+ provider registry.terraform.io/integrations/github v6.11.1

Affected Resource(s)

  • github_repository

Terraform Configuration Files

variable "repo_name" {
  description = "The name of the GitHub repository"
  type        = string
}

variable "github_repo_visibility" {
  description = "The visibility of the GitHub repository (e.g. public, private, internal)"
  type        = string
  default     = "private"
  validation {
        condition     = contains(["public", "private", "internal"], var.github_repo_visibility)
        error_message = "GitHub repository visibility must be either 'public', 'private', or 'internal'"
    }
}

variable "environment_name" {
    description = "The name of the environment (e.g. dev, prod)"
    type        = string
}

variable "github_organization" {
    description = "The name of the GitHub organization to create the repository in"
    type        = string
}

variable "description" {
  description = "A short description of the repository"
  type        = string
  default     = ""
}

variable "default_branch" {
  description = "Name of the default (main) branch"
  type        = string
  default     = "main"
}

variable "token_rotation_branch" {
  description = "Name of the branch used for token rotation"
  type        = string
  default     = "token-rotation"
}

variable "default_branch_files" {
  description = <<-EOT
    Map of files to commit to the default branch.
    Key is the file path within the repository (e.g. "README.md").
    Each object has:
      - content            : the file contents as a string
      - overwrite_on_create: whether to overwrite if the file already exists
  EOT
  type = map(object({
    content             = string
    overwrite_on_create = optional(bool, true)
  }))
  default = {}
}

variable "token_rotation_branch_files" {
  description = <<-EOT
    Map of files to commit to the toke rotation branch.
    Key is the file path within the repository (e.g. "README.md").
    Each object has:
      - content            : the file contents as a string
      - overwrite_on_create: whether to overwrite if the file already exists
  EOT
  type = map(object({
    content             = string
    overwrite_on_create = optional(bool, true)
  }))
  default = {}
}

variable "environments" {
  description = "List of GitHub Actions environment names to create (e.g. [\"dev\", \"prod\"])"
  type        = list(string)
  default     = ["dev", "prod"]
}

variable "environment_secrets" {
  description = <<-EOT
    Secrets to create per environment.
    Outer key is the environment name, inner key is the secret name, value is the plaintext secret value.
    Values are marked sensitive.
  EOT
  type = list(map(string))
  default  = []
  sensitive = true
}

variable "environment_variables" {
  description = <<-EOT
    Variables to create per environment.
    Outer key is the environment name, inner key is the variable name, value is the variable value.
  EOT
  type    = list(map(string))
  default = []
}

variable "maintainer_team_slug" {
  description = "Slug of the GitHub team to grant maintain permission on the repository"
  type        = string
}

variable "required_approving_review_count" {
  description = "Number of required approving reviews before a PR can be merged into the default branch"
  type        = number
  default     = 1
}


resource "github_repository" "this" {
  count                  = var.environment_name == "dev" ? 1 : 0
  name                   = var.repo_name
  description            = var.description
  visibility             = var.github_repo_visibility
  has_discussions        = true
  allow_merge_commit     = false
  allow_squash_merge     = true
  allow_rebase_merge     = false
  allow_auto_merge       = true
  delete_branch_on_merge = true
  # Initialise with an empty commit so branches can be created immediately
  auto_init = true
}

data "github_repository" "this_data" {
  count     = var.environment_name == "dev" ? 0 : 1
  full_name = "${var.github_organization}/${var.repo_name}"
}

resource "github_branch" "default" {
  count      = var.environment_name == "dev" ? 1 : 0
  repository = github_repository.this[0].name
  branch     = "main"

  depends_on = [github_repository.this]
}

resource "github_branch_default" "this" {
  count      = var.environment_name == "dev" ? 1 : 0
  repository = github_repository.this[0].name
  branch     = github_branch.default[0].branch
}

resource "github_branch" "token_rotation" {
  count         = var.environment_name == "dev" ? 1 : 0
  repository    = github_repository.this[0].name
  branch        = var.token_rotation_branch
  source_branch = github_branch_default.this[0].branch
}

# ─── Commit files to the default branch ──────────────────────────────────────

resource "github_repository_file" "commit_default_branch_files" {
  for_each            = var.default_branch_files

  repository          = github_repository.this[0].name
  branch              = github_branch_default.this[0].branch
  file                = each.key
  content             = each.value.content
  overwrite_on_create = each.value.overwrite_on_create

  commit_message = "chore: add ${each.key} via Terraform"
  commit_author  = "Terraform"
  commit_email   = "[email protected]"

  depends_on = [github_branch_default.this]
}


resource "github_repository_file" "commit_token_rotation_branch_files" {
  for_each = var.token_rotation_branch_files

  repository          = github_repository.this[0].name
  branch              = github_branch.token_rotation[0].branch
  file                = each.key
  content             = each.value.content
  overwrite_on_create = each.value.overwrite_on_create

  commit_message = "chore: add ${each.key} via Terraform"
  commit_author  = "Terraform"
  commit_email   = "[email protected]"

  depends_on = [github_branch_default.this]
}

# ─── Branch protection (main: PRs only, no direct push) ──────────────────────

resource "github_branch_protection" "main" {
  count         = var.environment_name == "dev" ? 1 : 0
  repository_id = github_repository.this[0].node_id
  pattern       = github_branch_default.this[0].branch

  enforce_admins         = true
  allows_deletions       = false
  allows_force_pushes    = false
  require_signed_commits = false

  required_pull_request_reviews {
    dismiss_stale_reviews           = true
    require_code_owner_reviews      = false
    required_approving_review_count = var.required_approving_review_count
  }

  # Branch protection must be applied after files are committed so the branch exists
  depends_on = [github_repository_file.commit_default_branch_files]
}

# ─── Environments ─────────────────────────────────────────────────────────────

resource "github_repository_environment" "envs" {
  for_each = toset(var.environments)

  repository  = github_repository.this[0].name
  environment = each.key
}

# ─── Environment secrets ──────────────────────────────────────────────────────

resource "github_actions_environment_secret" "secrets" {
  count = length(var.environment_secrets)

  repository      = var.environment_name == "dev" ? github_repository.this[0].name : data.github_repository.this_data[0]
  environment     = var.environment_secrets[count.index].environment
  secret_name     = var.environment_secrets[count.index].name
  plaintext_value = var.environment_secrets[count.index].value

  depends_on = [github_repository_environment.envs]
}

# ─── Environment variables ────────────────────────────────────────────────────

resource "github_actions_environment_variable" "variables" {
  count = length(var.environment_variables)

  repository    = var.environment_name == "dev" ? github_repository.this[0].name : data.github_repository.this_data[0]
  environment   = var.environment_variables[count.index].environment
  variable_name = var.environment_variables[count.index].name
  value         = var.environment_variables[count.index].value

  depends_on = [github_repository_environment.envs]
}

# ─── Team maintainers ─────────────────────────────────────────────────────────¬

resource "github_team_repository" "maintainers" {
  count      = var.environment_name == "dev" ? 1 : 0
  team_id    = var.maintainer_team_slug
  repository = github_repository.this[0].name
  permission = "maintain"
}

Steps to Reproduce

The above Terraform configuration file is stored as a module and I have this file that uses the module

module "github_repo" {
  source = "../../genesis-api/genesis_api/terraform_modules/github"

  environment_name       = "dev"
  github_repo_visibility = "public"
  github_organization    = "exnihilo-studio"
  repo_name              = "test_terraform_repo"
  description            = "Repository to test terraform delete"
  default_branch_files = {
    "README.md" = {
      content             = "This is a test repository."
      overwrite_on_create = true
    }
  }
  token_rotation_branch_files = {
    "README.md" = {
      content             = "README file for the token rotation branch."
      overwrite_on_create = true
    }
  }
  environments = ["dev", "prod"]
  environment_variables = [
    {
      "environment" = "dev",
      "name"        = "GCP_PROJECT_ID",
      "value"       = "some_value"
    }
  ]
  environment_secrets  = []
  maintainer_team_slug = "exnihilo-team"
}
  1. I run terraform apply -auto-approve to create the repo
  2. I comment out the above content and run terraform apply -auto-approve to delete the repo
  3. I also tried terraform destroy and got the same error

This is the plan for the terraform destroy which I run after the 2nd step

❯ terraform destroy
module.github_repo.github_repository.this[0]: Refreshing state... [id=test_terraform_repo]
module.github_repo.github_branch.default[0]: Refreshing state... [id=test_terraform_repo:main]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # module.github_repo.github_branch.default[0] will be destroyed
  - resource "github_branch" "default" {
      - branch        = "main" -> null
      - etag          = "W/\"44a566205da62dd406e48103fbd646d9d09c520c061b9fa8ba566ef5b4a9b80f\"" -> null
      - id            = "test_terraform_repo:main" -> null
      - ref           = "refs/heads/main" -> null
      - repository    = "test_terraform_repo" -> null
      - sha           = "c22aa321a8fe5f9870cd6f6566c8073fe1c7d69a" -> null
      - source_branch = "main" -> null
      - source_sha    = "a6ff23273f150f4b44ed5ca67a06d91f2c356bb1" -> null
    }

  # module.github_repo.github_repository.this[0] will be destroyed
  - resource "github_repository" "this" {
      - allow_auto_merge                        = true -> null
      - allow_forking                           = true -> null
      - allow_merge_commit                      = false -> null
      - allow_rebase_merge                      = false -> null
      - allow_squash_merge                      = true -> null
      - allow_update_branch                     = false -> null
      - archived                                = false -> null
      - auto_init                               = true -> null
      - default_branch                          = "main" -> null
      - delete_branch_on_merge                  = true -> null
      - description                             = "Repository to test terraform delete" -> null
      - etag                                    = "W/\"bd5bd280d5410275f45f32bf7ca749851807e6b52741e3c625ee66c2b7740a7b\"" -> null
      - fork                                    = "false" -> null
      - full_name                               = "exnihilo-studio/test_terraform_repo" -> null
      - git_clone_url                           = "git://github.com/exnihilo-studio/test_terraform_repo.git" -> null
      - has_discussions                         = true -> null
      - has_downloads                           = false -> null
      - has_issues                              = false -> null
      - has_projects                            = false -> null
      - has_wiki                                = false -> null
      - html_url                                = "https://github.com/exnihilo-studio/test_terraform_repo" -> null
      - http_clone_url                          = "https://github.com/exnihilo-studio/test_terraform_repo.git" -> null
      - id                                      = "test_terraform_repo" -> null
      - ignore_vulnerability_alerts_during_read = false -> null
      - is_template                             = false -> null
      - merge_commit_message                    = "PR_TITLE" -> null
      - merge_commit_title                      = "MERGE_MESSAGE" -> null
      - name                                    = "test_terraform_repo" -> null
      - node_id                                 = "R_kgDOSAb99A" -> null
      - private                                 = false -> null
      - repo_id                                 = 1208417780 -> null
      - squash_merge_commit_message             = "COMMIT_MESSAGES" -> null
      - squash_merge_commit_title               = "COMMIT_OR_PR_TITLE" -> null
      - ssh_clone_url                           = "[email protected]:exnihilo-studio/test_terraform_repo.git" -> null
      - svn_url                                 = "https://github.com/exnihilo-studio/test_terraform_repo" -> null
      - topics                                  = [] -> null
      - visibility                              = "public" -> null
      - vulnerability_alerts                    = true -> null
      - web_commit_signoff_required             = false -> null
        # (4 unchanged attributes hidden)

      - security_and_analysis {
          - secret_scanning {
              - status = "disabled" -> null
            }
          - secret_scanning_push_protection {
              - status = "disabled" -> null
            }
        }
    }

Plan: 0 to add, 0 to change, 2 to destroy.

Debug Output

module.github_repo.github_branch.default[0]: Destroying... [id=test_terraform_repo:main]
╷
│ Error: error deleting GitHub branch reference exnihilo-studio/test_terraform_repo (refs/heads/main): DELETE https://api.github.com/repos/exnihilo-studio/test_terraform_repo/git/refs/heads/main: 422 Cannot delete the default branch []
│ 
│ 
╵

Panic Output

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: TriageThis is being looked at and prioritizedType: BugSomething isn't working as documented

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions