integrations / integrations/terraform-provider-github

[BUG]: github_actions_organization_permissions cannot disable Actions for all organization repositories

Open
#3,417 4 comments 0 reactions 0 assignees View on GitHub
r/actions_organization_permissions Status: Triage Type: Bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.