integrations / integrations/terraform-provider-github
[BUG]: github_actions_organization_permissions cannot disable Actions for all organization repositories
- Dominant language
- Go
- Stars
- 1.2k
- Forks
- 1k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 8
Description
### Expected Behavior
The provider should send the equivalent of:
```
{
enabled_repositories": "none"
}
```
GitHub Actions should be disabled for all repositories in the organization.
### Actual Behavior
Terraform plans successfully:
```
+ resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "none"
+ id = (known after apply)
+ sha_pinning_required = (known after apply)
}
```
Apply fails:
```
Error: PUT https://api.example.ghe.com/orgs/example-org/actions/permissions: 422 Invalid request.
is not a member of ["all", "local_only", "selected"].
```
That validation error appears to refer to allowed_actions, not enabled_repositories.
### Terraform Version
Terraform v1.15.0
on darwin_arm64
+ provider registry.terraform.io/integrations/github v6.12.1
### Affected Resource(s)
- github_actions_organization_permissions
### Terraform Configuration Files
```hcl
terraform {
required_providers {
github = {
source = "integrations/github"
version = "6.12.1"
}
}
}
provider "github" {
owner = "example-org"
base_url = "https://api.example.ghe.com/"
token = var.github_token
}
variable "github_token" {
type = string
sensitive = true
}
with this setup:
resource "github_actions_organization_permissions" "test" {
enabled_repositories = "none"
}
Notably, these others also fail
resource "github_actions_organization_permissions" "test" {
enabled_repositories = "none"
allowed_actions = "all"
}
resource "github_actions_organization_permissions" "test" {
enabled_repositories = "none"
allowed_actions = "local_only"
}
resource "github_actions_organization_permissions" "test" {
enabled_repositories = "none"
allowed_actions = "selected"
allowed_actions_config {
github_owned_allowed = false
verified_allowed = false
patterns_allowed = []
}
}
and even
resource "github_actions_organization_permissions" "test" {
enabled_repositories = "none"
allowed_actions = "selected"
allowed_actions_config {
github_owned_allowed = false
verified_allowed = false
patterns_allowed = ["actions/checkout@*"]
}
}
```
### Steps to Reproduce
```
TF_VAR_github_token="" terraform apply
```
### Debug Output
```shell
I actually did more research than the above.
In `github/resource_github_actions_organization_permissions.go`, the provider always sets AllowedActions, even when allowed_actions is omitted:
allowedActions := d.Get("allowed_actions").(string)
enabledRepositories := d.Get("enabled_repositories").(string)
actionsPermissions := github.ActionsPermissions{
AllowedActions: &allowedActions,
EnabledRepositories: &enabledRepositories,
}
Because allowed_actions is optional, d.Get("allowed_actions").(string) returns "" when omitted. The provider then appears to send allowed_actions: "", which GitHub rejects because valid values are only all, local_only, and selected.
This differs from the nearby repository-level Actions permissions resource, which avoids sending allowed_actions when Actions are disabled:
allowedActions := d.Get("allowed_actions").(string)
enabled := d.Get("enabled").(bool)
repoActionPermissions := github.ActionsPermissionsRepository{
Enabled: &enabled,
}
// Only specify `allowed_actions` if actions are enabled
if enabled {
repoActionPermissions.AllowedActions = &allowedActions
}
That pattern seems appropriate for organization-level permissions too. When enabled_repositories = "none", allowed_actions should not be sent unless the API explicitly requires it.
The provider also uses GetOk / non-empty checks elsewhere for optional string fields, for example:
if v, ok := d.GetOk("description"); ok && len(v.(string)) > 0 {
milestone.Description = new(v.(string))
}
And this path is entirely untested. Current acceptance tests appear to cover:
enabled_repositories = "all"
enabled_repositories = "selected" with non-empty repository IDs
selected allowed-actions config
They do not appear to cover:
enabled_repositories = "none"
enabled_repositories = "selected" with repository_ids = []
Those two cases are important for deny-by-default organization baselines.
I tested this code change locally with Terraform provider development overrides, and it fixes the problem.
This configuration now applies successfully and immediately converges:
resource "github_actions_organization_permissions" "test" {
enabled_repositories = "none"
}
Observed API response after apply:
{"enabled_repositories":"none","sha_pinning_required":false}
Follow-up terraform plan result:
No changes. Your infrastructure matches the configuration.
This configuration also applies successfully and immediately converges:
resource "github_actions_organization_permissions" "test" {
enabled_repositories = "selected"
allowed_actions = "local_only"
enabled_repositories_config {
repository_ids = []
}
}
Observed API response after apply:
{
"enabled_repositories": "selected",
"allowed_actions": "local_only"
}
Observed selected repository list after apply:
{"total_count":0,"repositories":[]}
Follow-up terraform plan result:
No changes. Your infrastructure matches the configuration.
Sketch:
func resourceGithubActionsEnabledRepositoriesObject(d *schema.ResourceData) ([]int64, error) {
config := d.Get("enabled_repositories_config").([]any)
if len(config) == 0 || config[0] == nil {
return []int64{}, nil
}
data, ok := config[0].(map[string]any)
if !ok {
return []int64{}, nil
}
ids, ok := data["repository_ids"].(*schema.Set)
if !ok || ids == nil {
return []int64{}, nil
}
enabled := make([]int64, 0, ids.Len())
for _, value := range ids.List() {
enabled = append(enabled, int64(value.(int)))
}
return enabled, nil
}
```
### Panic Output
```shell
```
### Code of Conduct
- [x] I agree to follow this project's Code of Conduct
Contributor guide
Assessment
This issue has not been assessed yet.