diff --git a/RESOURCES.md b/RESOURCES.md
index b1b1c3e2b0..3390c36254 100644
--- a/RESOURCES.md
+++ b/RESOURCES.md
@@ -128,7 +128,7 @@ The overall status of each resource or data source is captured in this document
| `github_actions_environment_variable` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_actions_hosted_runner` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_actions_organization_oidc_subject_claim_customization_template` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
-| `github_actions_organization_permissions` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
+| `github_actions_organization_permissions` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_actions_organization_secret` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_actions_organization_secret_repositories` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_actions_organization_secret_repository` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
diff --git a/docs/resources/actions_organization_permissions.md b/docs/resources/actions_organization_permissions.md
index 14ab6c6857..429a176b3e 100644
--- a/docs/resources/actions_organization_permissions.md
+++ b/docs/resources/actions_organization_permissions.md
@@ -1,12 +1,13 @@
---
page_title: "github_actions_organization_permissions (Resource) - GitHub"
+subcategory: ""
description: |-
- Creates and manages Actions permissions within a GitHub organization
+ Resource to manage the GitHub Actions organization permissions.
---
# github_actions_organization_permissions (Resource)
-This resource allows you to create and manage GitHub Actions permissions within your GitHub enterprise organizations. You must have admin access to an organization to use this resource.
+Resource to manage the GitHub Actions organization permissions.
## Example Usage
@@ -15,48 +16,78 @@ resource "github_repository" "example" {
name = "my-repository"
}
-resource "github_actions_organization_permissions" "test" {
- allowed_actions = "selected"
+resource "github_actions_organization_permissions" "example" {
enabled_repositories = "selected"
+
+ enabled_repositories_config {
+ repository_ids = [github_repository.example.repo_id]
+ }
+
+ allowed_actions = "selected"
+
allowed_actions_config {
github_owned_allowed = true
patterns_allowed = ["actions/cache@*", "actions/checkout@*"]
verified_allowed = true
}
- enabled_repositories_config {
- repository_ids = [github_repository.example.repo_id]
- }
+
+ sha_pinning_required = true
}
```
-## Argument Reference
+
+## Schema
+
+### Required
+
+- `enabled_repositories` (String) Policy that controls the repositories in the organization that are allowed to run GitHub Actions; this can be one of `all`, `none`, or `selected`.
-The following arguments are supported:
+### Optional
-- `allowed_actions` - (Optional) The permissions policy that controls the actions that are allowed to run. Can be one of: `all`, `local_only`, or `selected`.
-- `enabled_repositories` - (Required) The policy that controls the repositories in the organization that are allowed to run GitHub Actions. Can be one of: `all`, `none`, or `selected`.
-- `allowed_actions_config` - (Optional) Sets the actions that are allowed in an organization. Only available when `allowed_actions` = `selected`. See [Allowed Actions Config](#allowed-actions-config) below for details.
-- `enabled_repositories_config` - (Optional) Sets the list of selected repositories that are enabled for GitHub Actions in an organization. Only available when `enabled_repositories` = `selected`. See [Enabled Repositories Config](#enabled-repositories-config) below for details.
-- `sha_pinning_required` - (Optional) Whether pinning to a specific SHA is required for all actions and reusable workflows in the organization.
+- `allowed_actions` (String) Policy that controls the actions that are allowed to run; this can be one of `all`, `local_only`, or `selected`.
+- `allowed_actions_config` (Block List, Max: 1) Configure the actions that are allowed in an organization; this is only supported when `allowed_actions` is set to `selected`. (see [below for nested schema](#nestedblock--allowed_actions_config))
+- `enabled_repositories_config` (Block List, Max: 1) Configure the repositories that are enabled for GitHub Actions in an organization; this is only supported when `enabled_repositories` is set to `selected`. (see [below for nested schema](#nestedblock--enabled_repositories_config))
+- `sha_pinning_required` (Boolean) If all actions and reusable workflows in an organization require pinning to a specific SHA.
-### Allowed Actions Config
+### Read-Only
-The `allowed_actions_config` block supports the following:
+- `id` (String) The ID of this resource.
-- `github_owned_allowed` - (Required) Whether GitHub-owned actions are allowed in the organization.
-- `patterns_allowed` - (Optional) Specifies a list of string-matching patterns to allow specific action(s). Wildcards, tags, and SHAs are allowed. For example, monalisa/octocat@*, monalisa/octocat@v2, monalisa/*."
-- `verified_allowed` - (Optional) Whether actions in GitHub Marketplace from verified creators are allowed. Set to true to allow all GitHub Marketplace actions by verified creators.
+
+### Nested Schema for `allowed_actions_config`
-### Enabled Repositories Config
+Required:
-The `enabled_repositories_config` block supports the following:
+- `github_owned_allowed` (Boolean) If GitHub-owned actions are allowed.
-- `repository_ids` - (Required) List of repository IDs to enable for GitHub Actions.
+Optional:
+
+- `patterns_allowed` (Set of String) List of string-matching patterns to allow specific actions; wildcards, tags, and SHAs are allowed. For example, `monalisa/octocat@*`, `monalisa/octocat@v2`, `monalisa/*`.
+- `verified_allowed` (Boolean) If actions in GitHub Marketplace from verified creators are allowed.
+
+
+
+### Nested Schema for `enabled_repositories_config`
+
+Required:
+
+- `repository_ids` (Set of Number) List of repository IDs to enable for GitHub Actions.
## Import
-This resource can be imported using the name of the GitHub organization:
+Import is supported using the following syntax:
+
+In Terraform v1.5.0 and later, the [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used with the `id` attribute, for example:
+
+```terraform
+import {
+ to = github_actions_organization_permissions.example
+ id = "org-name"
+}
+```
+
+The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
```shell
-terraform import github_actions_organization_permissions.test github_organization_name
+terraform import github_actions_organization_permissions.example org-name
```
diff --git a/examples/resources/github_actions_organization_permissions/import-by-string-id.tf b/examples/resources/github_actions_organization_permissions/import-by-string-id.tf
new file mode 100644
index 0000000000..ccf1cd74c0
--- /dev/null
+++ b/examples/resources/github_actions_organization_permissions/import-by-string-id.tf
@@ -0,0 +1,4 @@
+import {
+ to = github_actions_organization_permissions.example
+ id = "org-name"
+}
diff --git a/examples/resources/github_actions_organization_permissions/import.sh b/examples/resources/github_actions_organization_permissions/import.sh
new file mode 100644
index 0000000000..9ae72faf0c
--- /dev/null
+++ b/examples/resources/github_actions_organization_permissions/import.sh
@@ -0,0 +1 @@
+terraform import github_actions_organization_permissions.example org-name
diff --git a/examples/resources/actions_organization_permissions/example_1.tf b/examples/resources/github_actions_organization_permissions/resource_1.tf
similarity index 73%
rename from examples/resources/actions_organization_permissions/example_1.tf
rename to examples/resources/github_actions_organization_permissions/resource_1.tf
index fa97dab7c7..c72caf08c5 100644
--- a/examples/resources/actions_organization_permissions/example_1.tf
+++ b/examples/resources/github_actions_organization_permissions/resource_1.tf
@@ -2,15 +2,20 @@ resource "github_repository" "example" {
name = "my-repository"
}
-resource "github_actions_organization_permissions" "test" {
- allowed_actions = "selected"
+resource "github_actions_organization_permissions" "example" {
enabled_repositories = "selected"
+
+ enabled_repositories_config {
+ repository_ids = [github_repository.example.repo_id]
+ }
+
+ allowed_actions = "selected"
+
allowed_actions_config {
github_owned_allowed = true
patterns_allowed = ["actions/cache@*", "actions/checkout@*"]
verified_allowed = true
}
- enabled_repositories_config {
- repository_ids = [github_repository.example.repo_id]
- }
+
+ sha_pinning_required = true
}
diff --git a/github/resource_github_actions_organization_permissions.go b/github/resource_github_actions_organization_permissions.go
index 9692b5ccc8..7374e202e4 100644
--- a/github/resource_github_actions_organization_permissions.go
+++ b/github/resource_github_actions_organization_permissions.go
@@ -3,75 +3,81 @@ package github
import (
"context"
"errors"
- "log"
+ "fmt"
"github.com/google/go-github/v92/github"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
func resourceGithubActionsOrganizationPermissions() *schema.Resource {
return &schema.Resource{
- Create: resourceGithubActionsOrganizationPermissionsCreateOrUpdate,
- Read: resourceGithubActionsOrganizationPermissionsRead,
- Update: resourceGithubActionsOrganizationPermissionsCreateOrUpdate,
- Delete: resourceGithubActionsOrganizationPermissionsDelete,
+ CreateContext: resourceGithubActionsOrganizationPermissionsCreate,
+ ReadContext: resourceGithubActionsOrganizationPermissionsRead,
+ UpdateContext: resourceGithubActionsOrganizationPermissionsUpdate,
+ DeleteContext: resourceGithubActionsOrganizationPermissionsDelete,
Importer: &schema.ResourceImporter{
- StateContext: schema.ImportStatePassthroughContext,
+ StateContext: resourceGithubActionsOrganizationPermissionsImport,
},
+ CustomizeDiff: diffGithubActionsOrganizationPermissions,
+
+ Description: "Resource to manage the GitHub Actions organization permissions.",
+
Schema: map[string]*schema.Schema{
- "allowed_actions": {
- Type: schema.TypeString,
- Optional: true,
- Description: "The permissions policy that controls the actions that are allowed to run. Can be one of: 'all', 'local_only', or 'selected'.",
- ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"all", "local_only", "selected"}, false)),
- },
"enabled_repositories": {
Type: schema.TypeString,
Required: true,
- Description: "The policy that controls the repositories in the organization that are allowed to run GitHub Actions. Can be one of: 'all', 'none', or 'selected'.",
+ Description: "Policy that controls the repositories in the organization that are allowed to run GitHub Actions; this can be one of `all`, `none`, or `selected`.",
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"all", "none", "selected"}, false)),
},
- "allowed_actions_config": {
+ "enabled_repositories_config": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
- Description: "Sets the actions that are allowed in an organization. Only available when 'allowed_actions' = 'selected'",
+ Description: "Configure the repositories that are enabled for GitHub Actions in an organization; this is only supported when `enabled_repositories` is set to `selected`.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
- "github_owned_allowed": {
- Type: schema.TypeBool,
- Required: true,
- Description: "Whether GitHub-owned actions are allowed in the organization.",
- },
- "patterns_allowed": {
+ "repository_ids": {
Type: schema.TypeSet,
- Optional: true,
- Description: "Specifies a list of string-matching patterns to allow specific action(s). Wildcards, tags, and SHAs are allowed. For example, 'monalisa/octocat@', 'monalisa/octocat@v2', 'monalisa/'.",
- Elem: &schema.Schema{Type: schema.TypeString},
- Set: schema.HashString,
- },
- "verified_allowed": {
- Type: schema.TypeBool,
- Optional: true,
- Description: "Whether actions in GitHub Marketplace from verified creators are allowed. Set to 'true' to allow all GitHub Marketplace actions by verified creators.",
+ Description: "List of repository IDs to enable for GitHub Actions.",
+ Elem: &schema.Schema{Type: schema.TypeInt},
+ Required: true,
},
},
},
},
- "enabled_repositories_config": {
+ "allowed_actions": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Policy that controls the actions that are allowed to run; this can be one of `all`, `local_only`, or `selected`.",
+ ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"all", "local_only", "selected"}, false)),
+ },
+ "allowed_actions_config": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
- Description: "Sets the list of selected repositories that are enabled for GitHub Actions in an organization. Only available when 'enabled_repositories' = 'selected'.",
+ Description: "Configure the actions that are allowed in an organization; this is only supported when `allowed_actions` is set to `selected`.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
- "repository_ids": {
+ "github_owned_allowed": {
+ Type: schema.TypeBool,
+ Required: true, // TODO: Make optional in v7
+ Description: "If GitHub-owned actions are allowed.",
+ },
+ "verified_allowed": {
+ Type: schema.TypeBool,
+ Optional: true,
+ Description: "If actions in GitHub Marketplace from verified creators are allowed.",
+ },
+ "patterns_allowed": {
Type: schema.TypeSet,
- Description: "List of repository IDs to enable for GitHub Actions.",
- Elem: &schema.Schema{Type: schema.TypeInt},
- Required: true,
+ Optional: true,
+ Description: "List of string-matching patterns to allow specific actions; wildcards, tags, and SHAs are allowed. For example, `monalisa/octocat@*`, `monalisa/octocat@v2`, `monalisa/*`.",
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Set: schema.HashString,
},
},
},
@@ -79,253 +85,394 @@ func resourceGithubActionsOrganizationPermissions() *schema.Resource {
"sha_pinning_required": {
Type: schema.TypeBool,
Optional: true,
- Computed: true,
- Description: "Whether pinning to a specific SHA is required for all actions and reusable workflows in an organization.",
+ Computed: true, // TODO: Remove computed in v7
+ Description: "If all actions and reusable workflows in an organization require pinning to a specific SHA.",
},
},
}
}
-func resourceGithubActionsOrganizationAllowedObject(d *schema.ResourceData) *github.ActionsAllowed {
- allowed := &github.ActionsAllowed{}
+func diffGithubActionsOrganizationPermissions(ctx context.Context, diff *schema.ResourceDiff, m any) error {
+ if !diff.NewValueKnown("enabled_repositories") || !diff.NewValueKnown("enabled_repositories_config") || !diff.NewValueKnown("allowed_actions") || !diff.NewValueKnown("allowed_actions_config") {
+ return nil
+ }
- config := d.Get("allowed_actions_config").([]any)
- if len(config) > 0 {
- data := config[0].(map[string]any)
- switch x := data["github_owned_allowed"].(type) {
- case bool:
- allowed.GithubOwnedAllowed = &x
- }
+ enabledRepos, _ := diff.Get("enabled_repositories").(string)
+ enabledReposConfig := false
- switch x := data["verified_allowed"].(type) {
- case bool:
- allowed.VerifiedAllowed = &x
+ if v, ok := diff.GetOk("enabled_repositories_config"); ok {
+ if p, ok := v.([]any); ok && len(p) > 0 {
+ enabledReposConfig = true
}
+ }
- patternsAllowed := []string{}
+ if enabledRepos != "selected" && enabledReposConfig {
+ return fmt.Errorf("enabled_repositories_config is only valid when enabled_repositories is set to selected")
+ }
- switch t := data["patterns_allowed"].(type) {
- case *schema.Set:
- for _, value := range t.List() {
- patternsAllowed = append(patternsAllowed, value.(string))
- }
- }
+ if enabledRepos == "selected" && !enabledReposConfig {
+ return fmt.Errorf("enabled_repositories_config is required when enabled_repositories is set to selected")
+ }
- allowed.PatternsAllowed = patternsAllowed
- } else {
- return nil
+ allowedActions, _ := diff.Get("allowed_actions").(string)
+ allowedActionsConfig := false
+
+ if v, ok := diff.GetOk("allowed_actions_config"); ok {
+ if p, ok := v.([]any); ok && len(p) > 0 {
+ allowedActionsConfig = true
+ }
}
- return allowed
-}
+ if allowedActions != "" && enabledRepos == "none" {
+ return fmt.Errorf("allowed_actions is only valid when enabled_repositories is set to all or selected")
+ }
-func resourceGithubActionsEnabledRepositoriesObject(d *schema.ResourceData) ([]int64, error) {
- var enabled []int64
+ if allowedActions != "selected" && allowedActionsConfig {
+ return fmt.Errorf("allowed_actions_config is only valid when allowed_actions is set to selected")
+ }
- config := d.Get("enabled_repositories_config").([]any)
- if len(config) > 0 {
- data := config[0].(map[string]any)
- switch x := data["repository_ids"].(type) {
- case *schema.Set:
- for _, value := range x.List() {
- enabled = append(enabled, int64(value.(int)))
- }
- }
- } else {
- return nil, errors.New("the enabled_repositories_config {} block must be specified if enabled_repositories == 'selected'")
+ if allowedActions == "selected" && !allowedActionsConfig {
+ return fmt.Errorf("allowed_actions_config is required when allowed_actions is set to selected")
}
- return enabled, nil
+
+ return nil
}
-func resourceGithubActionsOrganizationPermissionsCreateOrUpdate(d *schema.ResourceData, m any) error {
+func resourceGithubActionsOrganizationPermissionsCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
meta, _ := m.(*Owner)
- client := meta.v3client
- orgName := meta.name
- ctx := context.Background()
- if !d.IsNewResource() {
- ctx = context.WithValue(ctx, ctxId, d.Id())
- }
- err := checkOrganization(meta)
- if err != nil {
- return err
+ if ok, diags := checkOrganizationOK(meta); !ok {
+ return diags
}
- allowedActions := d.Get("allowed_actions").(string)
- enabledRepositories := d.Get("enabled_repositories").(string)
+ enabledRepositories, _ := d.Get("enabled_repositories").(string)
- actionsPermissions := github.ActionsPermissions{
- AllowedActions: &allowedActions,
+ req := github.ActionsPermissions{
EnabledRepositories: &enabledRepositories,
}
- if v, ok := d.GetOk("sha_pinning_required"); ok {
- actionsPermissions.SHAPinningRequired = new(v.(bool))
+ if v, ok := d.GetOk("allowed_actions"); ok {
+ s, _ := v.(string)
+ req.AllowedActions = &s
}
- _, _, err = client.Actions.UpdateActionsPermissions(ctx,
- orgName,
- actionsPermissions)
- if err != nil {
- return err
+ if v, ok := d.GetOkExists("sha_pinning_required"); ok { //nolint:staticcheck // SA1019: d.GetOkExists is deprecated but necessary for bool fields
+ b, _ := v.(bool)
+ req.SHAPinningRequired = &b
}
- if allowedActions == "selected" {
- actionsAllowedData := resourceGithubActionsOrganizationAllowedObject(d)
- if actionsAllowedData != nil {
- log.Printf("[DEBUG] Allowed actions config is set")
- _, _, err = client.Actions.UpdateActionsAllowed(ctx,
- orgName,
- *actionsAllowedData)
- if err != nil {
- return err
- }
- } else {
- log.Printf("[DEBUG] Allowed actions config not set, skipping")
- }
+ if _, _, err := meta.v3client.Actions.UpdateActionsPermissions(ctx, meta.name, req); err != nil {
+ return diag.FromErr(err)
}
- if enabledRepositories == "selected" {
- enabledReposData, err := resourceGithubActionsEnabledRepositoriesObject(d)
+ if req.SHAPinningRequired == nil {
+ perm, _, err := meta.v3client.Actions.GetActionsPermissions(ctx, meta.name)
if err != nil {
- return err
+ return diag.FromErr(err)
}
- _, err = client.Actions.SetEnabledReposInOrg(ctx,
- orgName,
- enabledReposData)
- if err != nil {
- return err
+
+ if err := d.Set("sha_pinning_required", perm.GetSHAPinningRequired()); err != nil {
+ return diag.FromErr(err)
}
}
- d.SetId(orgName)
- return resourceGithubActionsOrganizationPermissionsRead(d, meta)
+ if *req.EnabledRepositories == "selected" {
+ if v, ok := d.GetOk("enabled_repositories_config"); ok {
+ if p, ok := v.([]any); ok && len(p) > 0 && p[0] != nil {
+ block, _ := p[0].(map[string]any)
+ col, _ := block["repository_ids"].(*schema.Set)
+
+ repoIDs := make([]int64, col.Len())
+ for i, a := range col.List() {
+ id, _ := a.(int)
+ repoIDs[i] = int64(id)
+ }
+
+ if _, err := meta.v3client.Actions.SetEnabledReposInOrg(ctx, meta.name, repoIDs); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ }
+ }
+
+ if req.AllowedActions != nil && *req.AllowedActions == "selected" {
+ if v, ok := d.GetOk("allowed_actions_config"); ok {
+ if p, ok := v.([]any); ok && len(p) > 0 && p[0] != nil {
+ block, _ := p[0].(map[string]any)
+
+ ownerAllowed, _ := block["github_owned_allowed"].(bool)
+
+ req := github.ActionsAllowed{
+ GithubOwnedAllowed: &ownerAllowed,
+ }
+
+ if v, ok := block["verified_allowed"]; ok {
+ if b, ok := v.(bool); ok {
+ req.VerifiedAllowed = &b
+ }
+ }
+
+ if v, ok := block["patterns_allowed"]; ok {
+ if p, ok := v.(*schema.Set); ok {
+ patterns := make([]string, p.Len())
+ for i, a := range p.List() {
+ patterns[i], _ = a.(string)
+ }
+ req.PatternsAllowed = patterns
+ }
+ }
+
+ if _, _, err := meta.v3client.Actions.UpdateActionsAllowed(ctx, meta.name, req); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ }
+ }
+
+ d.SetId(meta.name)
+
+ return nil
}
-func resourceGithubActionsOrganizationPermissionsRead(d *schema.ResourceData, m any) error {
+func resourceGithubActionsOrganizationPermissionsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
meta, _ := m.(*Owner)
- client := meta.v3client
- ctx := context.Background()
- err := checkOrganization(meta)
- if err != nil {
- return err
+ if ok, diags := checkOrganizationOK(meta); !ok {
+ return diags
}
- actionsPermissions, _, err := client.Actions.GetActionsPermissions(ctx, d.Id())
+ perm, _, err := meta.v3client.Actions.GetActionsPermissions(ctx, meta.name)
if err != nil {
- return err
+ return diag.FromErr(err)
}
- // only load and fill allowed_actions_config if allowed_actions_config is also set
- // in the TF code. (see #2105)
- // on initial import there might not be any value in the state, then we have to import the data
- // -> but we can only load an existing state if the current config is set to "selected" (see #2182)
- allowedActions := d.Get("allowed_actions").(string)
- allowedActionsConfig := d.Get("allowed_actions_config").([]any)
+ enabledRepositories := perm.GetEnabledRepositories()
+ if err := d.Set("enabled_repositories", enabledRepositories); err != nil {
+ return diag.FromErr(err)
+ }
- serverHasAllowedActionsConfig := actionsPermissions.GetAllowedActions() == "selected"
- userWantsAllowedActionsConfig := (allowedActions == "selected" && len(allowedActionsConfig) > 0) || allowedActions == ""
+ var reposCfg []any
+ if enabledRepositories == "selected" {
+ repoIDs := []int{}
- if serverHasAllowedActionsConfig && userWantsAllowedActionsConfig {
- actionsAllowed, _, err := client.Actions.GetActionsAllowed(ctx, d.Id())
- if err != nil {
- return err
+ for repo, err := range meta.v3client.Actions.ListEnabledReposInOrgIter(ctx, meta.name, &github.ListOptions{PerPage: meta.maxPerPage}) {
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ repoIDs = append(repoIDs, int(repo.GetID()))
}
- // If actionsAllowed set to local/all by removing all actions config settings, the response will be empty
- if actionsAllowed != nil {
- if err = d.Set("allowed_actions_config", []any{
- map[string]any{
- "github_owned_allowed": actionsAllowed.GetGithubOwnedAllowed(),
- "patterns_allowed": actionsAllowed.PatternsAllowed,
- "verified_allowed": actionsAllowed.GetVerifiedAllowed(),
- },
- }); err != nil {
- return err
+ reposCfg = []any{map[string]any{
+ "repository_ids": repoIDs,
+ }}
+ }
+
+ if err := d.Set("enabled_repositories_config", reposCfg); err != nil {
+ return diag.FromErr(err)
+ }
+
+ if _, ok := d.GetOk("allowed_actions"); ok {
+ allowedActions := perm.GetAllowedActions()
+ if err := d.Set("allowed_actions", allowedActions); err != nil {
+ return diag.FromErr(err)
+ }
+
+ var actionsCfg []any
+ if allowedActions == "selected" {
+ act, _, err := meta.v3client.Actions.GetActionsAllowed(ctx, meta.name)
+ if err != nil {
+ return diag.FromErr(err)
}
+
+ actionsCfg = []any{map[string]any{
+ "github_owned_allowed": act.GetGithubOwnedAllowed(),
+ "patterns_allowed": act.GetPatternsAllowed(),
+ "verified_allowed": act.GetVerifiedAllowed(),
+ }}
}
- } else {
- if err = d.Set("allowed_actions_config", []any{}); err != nil {
- return err
+
+ if err = d.Set("allowed_actions_config", actionsCfg); err != nil {
+ return diag.FromErr(err)
}
}
- if actionsPermissions.GetEnabledRepositories() == "selected" {
- opts := github.ListOptions{PerPage: meta.maxPerPage}
- var repoList []int64
- var allRepos []*github.Repository
+ if err := d.Set("sha_pinning_required", perm.GetSHAPinningRequired()); err != nil {
+ return diag.FromErr(err)
+ }
- for {
- enabledRepos, resp, err := client.Actions.ListEnabledReposInOrg(ctx, d.Id(), &opts)
- if err != nil {
- return err
- }
- allRepos = append(allRepos, enabledRepos.Repositories...)
+ return nil
+}
+
+func resourceGithubActionsOrganizationPermissionsUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
+ meta, _ := m.(*Owner)
+
+ if ok, diags := checkOrganizationOK(meta); !ok {
+ return diags
+ }
+
+ enabledRepositories, _ := d.Get("enabled_repositories").(string)
+ shaPinningRequired, _ := d.Get("sha_pinning_required").(bool) // TODO: Update for v7 when no longer computed
+
+ req := github.ActionsPermissions{
+ EnabledRepositories: &enabledRepositories,
+ SHAPinningRequired: &shaPinningRequired,
+ }
- opts.Page = resp.NextPage
+ if v, ok := d.GetOk("allowed_actions"); ok {
+ s, _ := v.(string)
+ req.AllowedActions = &s
+ }
+
+ if _, _, err := meta.v3client.Actions.UpdateActionsPermissions(ctx, meta.name, req); err != nil {
+ return diag.FromErr(err)
+ }
- if resp.NextPage == 0 {
- break
+ if *req.EnabledRepositories == "selected" {
+ if v, ok := d.GetOk("enabled_repositories_config"); ok {
+ if p, ok := v.([]any); ok && len(p) > 0 && p[0] != nil {
+ block, _ := p[0].(map[string]any)
+ col, _ := block["repository_ids"].(*schema.Set)
+
+ repoIDs := make([]int64, col.Len())
+ for i, a := range col.List() {
+ id, _ := a.(int)
+ repoIDs[i] = int64(id)
+ }
+
+ if _, err := meta.v3client.Actions.SetEnabledReposInOrg(ctx, meta.name, repoIDs); err != nil {
+ return diag.FromErr(err)
+ }
}
}
- for index := range allRepos {
- repoList = append(repoList, *allRepos[index].ID)
- }
- if allRepos != nil {
- if err = d.Set("enabled_repositories_config", []any{
- map[string]any{
- "repository_ids": repoList,
- },
- }); err != nil {
- return err
- }
- } else {
- if err = d.Set("enabled_repositories_config", []any{}); err != nil {
- return err
+ }
+
+ if req.AllowedActions != nil && *req.AllowedActions == "selected" {
+ if v, ok := d.GetOk("allowed_actions_config"); ok {
+ if p, ok := v.([]any); ok && len(p) > 0 && p[0] != nil {
+ block, _ := p[0].(map[string]any)
+
+ ownerAllowed, _ := block["github_owned_allowed"].(bool)
+
+ req := github.ActionsAllowed{
+ GithubOwnedAllowed: &ownerAllowed,
+ }
+
+ if v, ok := block["verified_allowed"]; ok {
+ if b, ok := v.(bool); ok {
+ req.VerifiedAllowed = &b
+ }
+ }
+
+ if v, ok := block["patterns_allowed"]; ok {
+ if p, ok := v.(*schema.Set); ok {
+ patterns := make([]string, p.Len())
+ for i, a := range p.List() {
+ patterns[i], _ = a.(string)
+ }
+ req.PatternsAllowed = patterns
+ }
+ }
+
+ if _, _, err := meta.v3client.Actions.UpdateActionsAllowed(ctx, meta.name, req); err != nil {
+ return diag.FromErr(err)
+ }
}
}
- } else {
- if err = d.Set("enabled_repositories_config", []any{}); err != nil {
- return err
- }
}
- if err = d.Set("allowed_actions", actionsPermissions.GetAllowedActions()); err != nil {
- return err
+ return nil
+}
+
+func resourceGithubActionsOrganizationPermissionsDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
+ meta, _ := m.(*Owner)
+
+ if ok, diags := checkOrganizationOK(meta); !ok {
+ return diags
}
- if err = d.Set("enabled_repositories", actionsPermissions.GetEnabledRepositories()); err != nil {
- return err
+
+ req := github.ActionsPermissions{
+ AllowedActions: new("all"),
+ EnabledRepositories: new("all"),
}
- if err = d.Set("sha_pinning_required", actionsPermissions.GetSHAPinningRequired()); err != nil {
- return err
+ if _, _, err := meta.v3client.Actions.UpdateActionsPermissions(ctx, meta.name, req); err != nil {
+ if ghErr, ok := errors.AsType[*github.ErrorResponse](err); ok {
+ if ghErr.Response.StatusCode == 409 {
+ tflog.Info(ctx, "Organization actions permissions couldn't be unset as they're already configured at enterprise level.")
+ return nil
+ }
+ }
+ return diag.FromErr(err)
}
return nil
}
-func resourceGithubActionsOrganizationPermissionsDelete(d *schema.ResourceData, m any) error {
+func resourceGithubActionsOrganizationPermissionsImport(ctx context.Context, d *schema.ResourceData, m any) ([]*schema.ResourceData, error) {
meta, _ := m.(*Owner)
- client := meta.v3client
- orgName := meta.name
- ctx := context.WithValue(context.Background(), ctxId, d.Id())
- err := checkOrganization(meta)
- if err != nil {
- return err
+ if d.Id() != meta.name {
+ return nil, fmt.Errorf("invalid id %q, expected %q", d.Id(), meta.name)
}
- // This will nullify any allowedActions elements
- _, _, err = client.Actions.UpdateActionsPermissions(ctx,
- orgName,
- github.ActionsPermissions{
- AllowedActions: new("all"),
- EnabledRepositories: new("all"),
- })
+ perm, _, err := meta.v3client.Actions.GetActionsPermissions(ctx, meta.name)
if err != nil {
- return err
+ return nil, err
}
- return nil
+ enabledRepositories := perm.GetEnabledRepositories()
+ if err := d.Set("enabled_repositories", enabledRepositories); err != nil {
+ return nil, err
+ }
+
+ var reposCfg []any
+ if enabledRepositories == "selected" {
+ repoIDs := []int{}
+
+ for repo, err := range meta.v3client.Actions.ListEnabledReposInOrgIter(ctx, meta.name, &github.ListOptions{PerPage: meta.maxPerPage}) {
+ if err != nil {
+ return nil, err
+ }
+
+ repoIDs = append(repoIDs, int(repo.GetID()))
+ }
+
+ reposCfg = []any{map[string]any{
+ "repository_ids": repoIDs,
+ }}
+ }
+
+ if err := d.Set("enabled_repositories_config", reposCfg); err != nil {
+ return nil, err
+ }
+
+ allowedActions := perm.GetAllowedActions()
+ if err := d.Set("allowed_actions", allowedActions); err != nil {
+ return nil, err
+ }
+
+ var actionsCfg []any
+ if allowedActions == "selected" {
+ act, _, err := meta.v3client.Actions.GetActionsAllowed(ctx, meta.name)
+ if err != nil {
+ return nil, err
+ }
+
+ actionsCfg = []any{map[string]any{
+ "github_owned_allowed": act.GetGithubOwnedAllowed(),
+ "patterns_allowed": act.GetPatternsAllowed(),
+ "verified_allowed": act.GetVerifiedAllowed(),
+ }}
+ }
+
+ if err = d.Set("allowed_actions_config", actionsCfg); err != nil {
+ return nil, err
+ }
+
+ if err := d.Set("sha_pinning_required", perm.GetSHAPinningRequired()); err != nil {
+ return nil, err
+ }
+
+ return []*schema.ResourceData{d}, nil
}
diff --git a/github/resource_github_actions_organization_permissions_test.go b/github/resource_github_actions_organization_permissions_test.go
index 2b88a58da2..a0ebb3340f 100644
--- a/github/resource_github_actions_organization_permissions_test.go
+++ b/github/resource_github_actions_organization_permissions_test.go
@@ -2,6 +2,7 @@ package github
import (
"fmt"
+ "regexp"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -13,41 +14,50 @@ import (
func TestAccGithubActionsOrganizationPermissions(t *testing.T) {
// IMPORTANT: Do not run these tests in parallel as they modify the organization state.
+ skipUnlessHasOrgs(t)
+
t.Run("full_lifecycle", func(t *testing.T) {
repo := mustCreateTestRepository(t)
+ configNone := `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "none"
+}
+`
+
configMinimal := `
resource "github_actions_organization_permissions" "test" {
- allowed_actions = "all"
- enabled_repositories = "all"
+ enabled_repositories = "%s"
+
+ allowed_actions = "%s"
}
`
- configFull := fmt.Sprintf(`
+ configFull := `
resource "github_actions_organization_permissions" "test" {
- allowed_actions = "selected"
enabled_repositories = "selected"
+ enabled_repositories_config {
+ repository_ids = [%v]
+ }
+
+ allowed_actions = "selected"
+
allowed_actions_config {
github_owned_allowed = true
patterns_allowed = ["actions/cache@*", "actions/checkout@*"]
verified_allowed = true
}
- enabled_repositories_config {
- repository_ids = [%d]
- }
-
sha_pinning_required = true
}
-`, repo.GetID())
+`
resource.Test(t, resource.TestCase{
- PreCheck: func() { skipUnlessHasOrgs(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: configMinimal,
+ Config: fmt.Sprintf(configMinimal, "all", "all"),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("github_actions_organization_permissions.test", tfjsonpath.New("allowed_actions_config"), knownvalue.ListSizeExact(0)),
statecheck.ExpectKnownValue("github_actions_organization_permissions.test", tfjsonpath.New("enabled_repositories_config"), knownvalue.ListSizeExact(0)),
@@ -55,7 +65,16 @@ resource "github_actions_organization_permissions" "test" {
},
},
{
- Config: configFull,
+ Config: configNone,
+ },
+ {
+ Config: fmt.Sprintf(configMinimal, "all", "local_only"),
+ },
+ {
+ Config: fmt.Sprintf(configFull, ""),
+ },
+ {
+ Config: fmt.Sprintf(configFull, repo.GetID()),
},
{
ResourceName: "github_actions_organization_permissions.test",
@@ -65,4 +84,177 @@ resource "github_actions_organization_permissions" "test" {
},
})
})
+
+ t.Run("disabled_actions", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "none"
+
+ allowed_actions = "all"
+}`,
+ ExpectError: regexp.MustCompile(regexp.QuoteMeta("allowed_actions is only valid when enabled_repositories is set to all or selected")),
+ },
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "none"
+}`,
+ },
+ },
+ })
+ })
+
+ t.Run("sha_pinning_can_be_disabled", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "all"
+
+ sha_pinning_required = true
+}`,
+ },
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "all"
+
+ sha_pinning_required = false
+}`,
+ },
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "all"
+
+ sha_pinning_required = true
+}`,
+ },
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "all"
+}`,
+ },
+ },
+ })
+ })
+
+ t.Run("sha_pinning_can_be_enabled", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "all"
+}`,
+ },
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "all"
+
+ sha_pinning_required = true
+}`,
+ },
+ },
+ })
+ })
+
+ t.Run("invalid_repository_config_set", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: `
+ resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ enabled_repositories_config {
+ repository_ids = []
+ }
+
+ allowed_actions = "all"
+}`,
+ ExpectError: regexp.MustCompile(regexp.QuoteMeta("enabled_repositories_config is only valid when enabled_repositories is set to selected")),
+ },
+ },
+ })
+ })
+
+ t.Run("invalid_repository_config_missing", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "selected"
+
+ allowed_actions = "all"
+}`,
+ ExpectError: regexp.MustCompile(regexp.QuoteMeta("enabled_repositories_config is required when enabled_repositories is set to selected")),
+ },
+ },
+ })
+ })
+
+ t.Run("invalid_actions_config_set", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: `
+ resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "all"
+
+ allowed_actions_config {
+ github_owned_allowed = true
+ patterns_allowed = ["actions/cache@*", "actions/checkout@*"]
+ verified_allowed = true
+ }
+}`,
+ ExpectError: regexp.MustCompile(regexp.QuoteMeta("allowed_actions_config is only valid when allowed_actions is set to selected")),
+ },
+ },
+ })
+ })
+
+ t.Run("invalid_actions_config_missing", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: `
+resource "github_actions_organization_permissions" "test" {
+ enabled_repositories = "all"
+
+ allowed_actions = "selected"
+}`,
+ ExpectError: regexp.MustCompile(regexp.QuoteMeta("allowed_actions_config is required when allowed_actions is set to selected")),
+ },
+ },
+ })
+ })
}
diff --git a/templates/resources/actions_organization_permissions.md.tmpl b/templates/resources/actions_organization_permissions.md.tmpl
index d15662e05a..759f56a64d 100644
--- a/templates/resources/actions_organization_permissions.md.tmpl
+++ b/templates/resources/actions_organization_permissions.md.tmpl
@@ -1,45 +1,47 @@
---
page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}"
+subcategory: ""
description: |-
- Creates and manages Actions permissions within a GitHub organization
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
---
# {{.Name}} ({{.Type}})
-This resource allows you to create and manage GitHub Actions permissions within your GitHub enterprise organizations. You must have admin access to an organization to use this resource.
+{{ .Description | trimspace }}
+{{ if .HasExamples -}}
## Example Usage
-{{ tffile "examples/resources/actions_organization_permissions/example_1.tf" }}
+{{- range .ExampleFiles }}
-## Argument Reference
+{{ tffile . }}
+{{- end }}
+{{- end }}
-The following arguments are supported:
+{{ .SchemaMarkdown | trimspace }}
+{{- if or .HasImport .HasImportIDConfig .HasImportIdentityConfig }}
-- `allowed_actions` - (Optional) The permissions policy that controls the actions that are allowed to run. Can be one of: `all`, `local_only`, or `selected`.
-- `enabled_repositories` - (Required) The policy that controls the repositories in the organization that are allowed to run GitHub Actions. Can be one of: `all`, `none`, or `selected`.
-- `allowed_actions_config` - (Optional) Sets the actions that are allowed in an organization. Only available when `allowed_actions` = `selected`. See [Allowed Actions Config](#allowed-actions-config) below for details.
-- `enabled_repositories_config` - (Optional) Sets the list of selected repositories that are enabled for GitHub Actions in an organization. Only available when `enabled_repositories` = `selected`. See [Enabled Repositories Config](#enabled-repositories-config) below for details.
-- `sha_pinning_required` - (Optional) Whether pinning to a specific SHA is required for all actions and reusable workflows in the organization.
-
-### Allowed Actions Config
+## Import
-The `allowed_actions_config` block supports the following:
+Import is supported using the following syntax:
+{{- end }}
+{{- if .HasImportIdentityConfig }}
-- `github_owned_allowed` - (Required) Whether GitHub-owned actions are allowed in the organization.
-- `patterns_allowed` - (Optional) Specifies a list of string-matching patterns to allow specific action(s). Wildcards, tags, and SHAs are allowed. For example, monalisa/octocat@*, monalisa/octocat@v2, monalisa/*."
-- `verified_allowed` - (Optional) Whether actions in GitHub Marketplace from verified creators are allowed. Set to true to allow all GitHub Marketplace actions by verified creators.
+In Terraform v1.12.0 and later, the [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used with the `identity` attribute, for example:
-### Enabled Repositories Config
+{{tffile .ImportIdentityConfigFile }}
-The `enabled_repositories_config` block supports the following:
+{{ .IdentitySchemaMarkdown | trimspace }}
+{{- end }}
+{{- if .HasImportIDConfig }}
-- `repository_ids` - (Required) List of repository IDs to enable for GitHub Actions.
+In Terraform v1.5.0 and later, the [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used with the `id` attribute, for example:
-## Import
+{{tffile .ImportIDConfigFile }}
+{{- end }}
+{{- if .HasImport }}
-This resource can be imported using the name of the GitHub organization:
+The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
-```shell
-terraform import github_actions_organization_permissions.test github_organization_name
-```
+{{codefile "shell" .ImportFile }}
+{{- end }}