From 18da1bb210b340785b4f86012d7321f5e417f55d Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 22 Dec 2025 11:53:15 +0200 Subject: [PATCH 1/8] Add resource for Organization Actions Workflow Permissions Signed-off-by: Timo Sand --- github/provider.go | 1 + ...tions_organization_workflow_permissions.go | 146 ++++++++++++++++++ ..._organization_workflow_permissions_test.go | 137 ++++++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 github/resource_github_actions_organization_workflow_permissions.go create mode 100644 github/resource_github_actions_organization_workflow_permissions_test.go diff --git a/github/provider.go b/github/provider.go index 3a3b24863c..b54e76d82f 100644 --- a/github/provider.go +++ b/github/provider.go @@ -212,6 +212,7 @@ func Provider() *schema.Provider { "github_enterprise_organization": resourceGithubEnterpriseOrganization(), "github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(), "github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(), + "github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(), "github_enterprise_security_analysis_settings": resourceGithubEnterpriseSecurityAnalysisSettings(), "github_workflow_repository_permissions": resourceGithubWorkflowRepositoryPermissions(), }, diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go new file mode 100644 index 0000000000..aadcab6fe7 --- /dev/null +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -0,0 +1,146 @@ +package github + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + + "github.com/google/go-github/v67/github" + "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" +) + +type GithubActionsOrganizationWorkflowPermissionsErrorResponse struct { + Message string `json:"message"` + Errors string `json:"errors"` + DocumentationURL string `json:"documentation_url"` + Status string `json:"status"` +} + +func resourceGithubActionsOrganizationWorkflowPermissions() *schema.Resource { + return &schema.Resource{ + Description: "GitHub Actions Organization Workflow Permissions management.", + CreateContext: resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate, + ReadContext: resourceGithubActionsOrganizationWorkflowPermissionsRead, + UpdateContext: resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate, + DeleteContext: resourceGithubActionsOrganizationWorkflowPermissionsDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "organization_slug": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The slug of the Organization.", + }, + "default_workflow_permissions": { + Type: schema.TypeString, + Optional: true, + Default: "read", + Description: "The default workflow permissions granted to the GITHUB_TOKEN when running workflows in any repository in the organization. Can be 'read' or 'write'.", + ValidateFunc: validation.StringInSlice([]string{"read", "write"}, false), + }, + "can_approve_pull_request_reviews": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether GitHub Actions can approve pull request reviews in any repository in the organization.", + }, + }, + } +} + +func handleEditWorkflowPermissionsError(err error, resp *github.Response) diag.Diagnostics { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) { + if ghErr.Response.StatusCode == http.StatusConflict { + errorResponse := &GithubActionsOrganizationWorkflowPermissionsErrorResponse{} + data, readError := io.ReadAll(resp.Body) + if readError == nil && data != nil { + unmarshalError := json.Unmarshal(data, errorResponse) + if unmarshalError != nil { + return diag.FromErr(unmarshalError) + } + } + return diag.FromErr(fmt.Errorf("you are trying to modify a value restricted by the Enterprise's settings.\n Message: %s\n Errors: %s\n Documentation URL: %s\n Status: %s\nerr: %w", errorResponse.Message, errorResponse.Errors, errorResponse.DocumentationURL, errorResponse.Status, err)) + } + } + return diag.FromErr(err) +} + +func resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + organizationSlug := d.Get("organization_slug").(string) + d.SetId(organizationSlug) + + workflowPerms := github.DefaultWorkflowPermissionOrganization{} + + if v, ok := d.GetOk("default_workflow_permissions"); ok { + workflowPerms.DefaultWorkflowPermissions = github.String(v.(string)) + } + + if v, ok := d.GetOk("can_approve_pull_request_reviews"); ok { + workflowPerms.CanApprovePullRequestReviews = github.Bool(v.(bool)) + } + + log.Printf("[DEBUG] Updating workflow permissions for Organization: %s", organizationSlug) + _, resp, err := client.Actions.EditDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) + if err != nil { + return handleEditWorkflowPermissionsError(err, resp) + } + + // Calling read is necessary as the Update API returns 204 with Empty Body on success + return resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx, d, meta) +} + +func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + organizationSlug := d.Id() + log.Printf("[DEBUG] Reading workflow permissions for Organization: %s", organizationSlug) + + workflowPerms, _, err := client.Actions.GetDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug) + if err != nil { + return diag.FromErr(err) + } + + if err := d.Set("organization_slug", organizationSlug); err != nil { + return diag.FromErr(err) + } + if err := d.Set("default_workflow_permissions", workflowPerms.DefaultWorkflowPermissions); err != nil { + return diag.FromErr(err) + } + if err := d.Set("can_approve_pull_request_reviews", workflowPerms.CanApprovePullRequestReviews); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + organizationSlug := d.Id() + log.Printf("[DEBUG] Resetting workflow permissions to defaults for Organization: %s", organizationSlug) + + // Reset to safe defaults + workflowPerms := github.DefaultWorkflowPermissionOrganization{ + DefaultWorkflowPermissions: github.String("read"), + CanApprovePullRequestReviews: github.Bool(false), + } + + _, resp, err := client.Actions.EditDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) + if err != nil { + return handleEditWorkflowPermissionsError(err, resp) + } + + return nil +} diff --git a/github/resource_github_actions_organization_workflow_permissions_test.go b/github/resource_github_actions_organization_workflow_permissions_test.go new file mode 100644 index 0000000000..1d33e8fc03 --- /dev/null +++ b/github/resource_github_actions_organization_workflow_permissions_test.go @@ -0,0 +1,137 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { + t.Run("creates organization workflow permissions without error", func(t *testing.T) { + config := fmt.Sprintf(` + resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" + + default_workflow_permissions = "read" + can_approve_pull_request_reviews = false + } + `, testOrganization) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "organization_slug", testOrganization), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "default_workflow_permissions", "read"), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "can_approve_pull_request_reviews", "false"), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + } + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) + + t.Run("updates organization workflow permissions without error", func(t *testing.T) { + configs := map[string]string{ + "before": fmt.Sprintf(` + resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" + + default_workflow_permissions = "read" + can_approve_pull_request_reviews = false + } + `, testOrganization), + + "after": fmt.Sprintf(` + resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" + + default_workflow_permissions = "write" // This change might be restricted by the Enterprise's settings + can_approve_pull_request_reviews = true + } + `, testOrganization), + } + + checks := map[string]resource.TestCheckFunc{ + "before": resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "default_workflow_permissions", "read"), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "can_approve_pull_request_reviews", "false"), + ), + "after": resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "default_workflow_permissions", "write"), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "can_approve_pull_request_reviews", "true"), + ), + } + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: configs["before"], + Check: checks["before"], + }, + { + Config: configs["after"], + Check: checks["after"], + }, + }, + }) + } + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) + + t.Run("imports enterprise workflow permissions without error", func(t *testing.T) { + config := fmt.Sprintf(` + resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" + + default_workflow_permissions = "read" + can_approve_pull_request_reviews = false + } + `, testOrganization) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "organization_slug", testOrganization), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "default_workflow_permissions", "read"), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "can_approve_pull_request_reviews", "false"), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + { + ResourceName: "github_actions_organization_workflow_permissions.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + } + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) +} From 6264f7a2a96524035cd12fe757b592ab76653dab Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 22 Dec 2025 12:03:43 +0200 Subject: [PATCH 2/8] Update documentation Signed-off-by: Timo Sand --- ...tions_organization_workflow_permissions.go | 2 +- ...ization_workflow_permissions.html.markdown | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 website/docs/r/actions_organization_workflow_permissions.html.markdown diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go index aadcab6fe7..32af92c54e 100644 --- a/github/resource_github_actions_organization_workflow_permissions.go +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -24,7 +24,7 @@ type GithubActionsOrganizationWorkflowPermissionsErrorResponse struct { func resourceGithubActionsOrganizationWorkflowPermissions() *schema.Resource { return &schema.Resource{ - Description: "GitHub Actions Organization Workflow Permissions management.", + Description: "This resource allows you to manage GitHub Actions workflow permissions for a GitHub Organization account. This controls the default permissions granted to the GITHUB_TOKEN when running workflows and whether GitHub Actions can approve pull request reviews.\n\nYou must have organization admin access to use this resource.", CreateContext: resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate, ReadContext: resourceGithubActionsOrganizationWorkflowPermissionsRead, UpdateContext: resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate, diff --git a/website/docs/r/actions_organization_workflow_permissions.html.markdown b/website/docs/r/actions_organization_workflow_permissions.html.markdown new file mode 100644 index 0000000000..ece7f34808 --- /dev/null +++ b/website/docs/r/actions_organization_workflow_permissions.html.markdown @@ -0,0 +1,65 @@ +--- +layout: "github" +page_title: "GitHub: github_actions_organization_workflow_permissions" +description: |- + Manages GitHub Actions workflow permissions for a GitHub Organization. +--- + +# github_actions_organization_workflow_permissions + +This resource allows you to manage GitHub Actions workflow permissions for a GitHub Organization account. This controls the default permissions granted to the GITHUB_TOKEN when running workflows and whether GitHub Actions can approve pull request reviews. + +You must have organization admin access to use this resource. + +## Example Usage + +```hcl +# Basic workflow permissions configuration +resource "github_actions_organization_workflow_permissions" "example" { + organization_slug = "my-organization" + + default_workflow_permissions = "read" + can_approve_pull_request_reviews = false +} + +# Allow write permissions and PR approvals +resource "github_actions_organization_workflow_permissions" "permissive" { + organization_slug = "my-organization" + + default_workflow_permissions = "write" + can_approve_pull_request_reviews = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `organization_slug` - (Required) The slug of the organization. + +* `default_workflow_permissions` - (Optional) The default workflow permissions granted to the GITHUB_TOKEN when running workflows. Can be `read` or `write`. Defaults to `read`. + +* `can_approve_pull_request_reviews` - (Optional) Whether GitHub Actions can approve pull request reviews. Defaults to `false`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The organization slug. + +## Import + +Organization Actions workflow permissions can be imported using the organization slug: + +```sh +terraform import github_actions_organization_workflow_permissions.example my-organization +``` + +## Notes + +~> **Note:** This resource requires a GitHub Organization account and organization admin permissions. + +When this resource is destroyed, the workflow permissions will be reset to safe defaults: + +* `default_workflow_permissions` = `read` +* `can_approve_pull_request_reviews` = `false` From 282282c811fdd9b0c19a0112e336a47a4fee30cc Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Wed, 7 Jan 2026 21:02:04 +0200 Subject: [PATCH 3/8] Upgrade to use v81 of `go-github` Signed-off-by: Timo Sand --- ...github_actions_organization_workflow_permissions.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go index 32af92c54e..5c4196a422 100644 --- a/github/resource_github_actions_organization_workflow_permissions.go +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -9,7 +9,7 @@ import ( "log" "net/http" - "github.com/google/go-github/v67/github" + "github.com/google/go-github/v81/github" "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" @@ -92,7 +92,7 @@ func resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate(ctx cont } log.Printf("[DEBUG] Updating workflow permissions for Organization: %s", organizationSlug) - _, resp, err := client.Actions.EditDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) + _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) if err != nil { return handleEditWorkflowPermissionsError(err, resp) } @@ -133,11 +133,11 @@ func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Cont // Reset to safe defaults workflowPerms := github.DefaultWorkflowPermissionOrganization{ - DefaultWorkflowPermissions: github.String("read"), - CanApprovePullRequestReviews: github.Bool(false), + DefaultWorkflowPermissions: github.Ptr("read"), + CanApprovePullRequestReviews: github.Ptr(false), } - _, resp, err := client.Actions.EditDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) + _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) if err != nil { return handleEditWorkflowPermissionsError(err, resp) } From 27dc3177aa13010a9c30b35053d7ba3fa3793fd3 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Wed, 7 Jan 2026 21:03:19 +0200 Subject: [PATCH 4/8] Align tests with new test structure Signed-off-by: Timo Sand --- ...tions_organization_workflow_permissions.go | 4 +- ..._organization_workflow_permissions_test.go | 98 ++++++++----------- 2 files changed, 42 insertions(+), 60 deletions(-) diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go index 5c4196a422..d4ac96e107 100644 --- a/github/resource_github_actions_organization_workflow_permissions.go +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -84,11 +84,11 @@ func resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate(ctx cont workflowPerms := github.DefaultWorkflowPermissionOrganization{} if v, ok := d.GetOk("default_workflow_permissions"); ok { - workflowPerms.DefaultWorkflowPermissions = github.String(v.(string)) + workflowPerms.DefaultWorkflowPermissions = github.Ptr(v.(string)) } if v, ok := d.GetOk("can_approve_pull_request_reviews"); ok { - workflowPerms.CanApprovePullRequestReviews = github.Bool(v.(bool)) + workflowPerms.CanApprovePullRequestReviews = github.Ptr(v.(bool)) } log.Printf("[DEBUG] Updating workflow permissions for Organization: %s", organizationSlug) diff --git a/github/resource_github_actions_organization_workflow_permissions_test.go b/github/resource_github_actions_organization_workflow_permissions_test.go index 1d33e8fc03..b539292faf 100644 --- a/github/resource_github_actions_organization_workflow_permissions_test.go +++ b/github/resource_github_actions_organization_workflow_permissions_test.go @@ -16,29 +16,23 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { default_workflow_permissions = "read" can_approve_pull_request_reviews = false } - `, testOrganization) + `, testAccConf.owner) check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "organization_slug", testOrganization), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "organization_slug", testAccConf.owner), resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "default_workflow_permissions", "read"), resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "can_approve_pull_request_reviews", "false"), ) - testCase := func(t *testing.T, mode string) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnlessMode(t, mode) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: config, - Check: check, - }, + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, }, - }) - } - - t.Run("with an organization account", func(t *testing.T) { - testCase(t, organization) + }, }) }) @@ -51,7 +45,7 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { default_workflow_permissions = "read" can_approve_pull_request_reviews = false } - `, testOrganization), + `, testAccConf.owner), "after": fmt.Sprintf(` resource "github_actions_organization_workflow_permissions" "test" { @@ -60,7 +54,7 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { default_workflow_permissions = "write" // This change might be restricted by the Enterprise's settings can_approve_pull_request_reviews = true } - `, testOrganization), + `, testAccConf.owner), } checks := map[string]resource.TestCheckFunc{ @@ -74,29 +68,23 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { ), } - testCase := func(t *testing.T, mode string) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnlessMode(t, mode) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: configs["before"], - Check: checks["before"], - }, - { - Config: configs["after"], - Check: checks["after"], - }, + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: configs["before"], + Check: checks["before"], }, - }) - } - - t.Run("with an organization account", func(t *testing.T) { - testCase(t, organization) + { + Config: configs["after"], + Check: checks["after"], + }, + }, }) }) - t.Run("imports enterprise workflow permissions without error", func(t *testing.T) { + t.Run("imports organization workflow permissions without error", func(t *testing.T) { config := fmt.Sprintf(` resource "github_actions_organization_workflow_permissions" "test" { organization_slug = "%s" @@ -104,34 +92,28 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { default_workflow_permissions = "read" can_approve_pull_request_reviews = false } - `, testOrganization) + `, testAccConf.owner) check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "organization_slug", testOrganization), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "organization_slug", testAccConf.owner), resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "default_workflow_permissions", "read"), resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "can_approve_pull_request_reviews", "false"), ) - testCase := func(t *testing.T, mode string) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnlessMode(t, mode) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: config, - Check: check, - }, - { - ResourceName: "github_actions_organization_workflow_permissions.test", - ImportState: true, - ImportStateVerify: true, - }, + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, }, - }) - } - - t.Run("with an organization account", func(t *testing.T) { - testCase(t, organization) + { + ResourceName: "github_actions_organization_workflow_permissions.test", + ImportState: true, + ImportStateVerify: true, + }, + }, }) }) } From 26109ef9c5168bff10e8fcd08f18fb4414b0c1ae Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Wed, 7 Jan 2026 21:11:08 +0200 Subject: [PATCH 5/8] Add further tests Signed-off-by: Timo Sand --- ..._organization_workflow_permissions_test.go | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/github/resource_github_actions_organization_workflow_permissions_test.go b/github/resource_github_actions_organization_workflow_permissions_test.go index b539292faf..f1487068d6 100644 --- a/github/resource_github_actions_organization_workflow_permissions_test.go +++ b/github/resource_github_actions_organization_workflow_permissions_test.go @@ -116,4 +116,51 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { }, }) }) + + t.Run("deletes organization workflow permissions without error", func(t *testing.T) { + config := fmt.Sprintf(` + resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" + + default_workflow_permissions = "write" + can_approve_pull_request_reviews = true + } + `, testAccConf.owner) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Destroy: true, + }, + }, + }) + }) + + t.Run("creates with minimal config using defaults", func(t *testing.T) { + config := fmt.Sprintf(` + resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" + } + `, testAccConf.owner) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "organization_slug", testAccConf.owner), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "default_workflow_permissions", "read"), + resource.TestCheckResourceAttr("github_actions_organization_workflow_permissions.test", "can_approve_pull_request_reviews", "false"), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + }) } From 9b4f634680e3c683a63f104516a43e00af445626 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Wed, 7 Jan 2026 21:23:48 +0200 Subject: [PATCH 6/8] Refactor to use `tflog` Signed-off-by: Timo Sand --- ..._actions_organization_workflow_permissions.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go index d4ac96e107..1ff4dffea2 100644 --- a/github/resource_github_actions_organization_workflow_permissions.go +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -6,10 +6,10 @@ import ( "errors" "fmt" "io" - "log" "net/http" "github.com/google/go-github/v81/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" @@ -91,7 +91,11 @@ func resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate(ctx cont workflowPerms.CanApprovePullRequestReviews = github.Ptr(v.(bool)) } - log.Printf("[DEBUG] Updating workflow permissions for Organization: %s", organizationSlug) + tflog.Debug(ctx, "Updating workflow permissions for Organization", map[string]any{ + "organization_slug": organizationSlug, + "default_workflow_permissions": workflowPerms.DefaultWorkflowPermissions, + "can_approve_pull_request_reviews": workflowPerms.CanApprovePullRequestReviews, + }) _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) if err != nil { return handleEditWorkflowPermissionsError(err, resp) @@ -105,7 +109,9 @@ func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Contex client := meta.(*Owner).v3client organizationSlug := d.Id() - log.Printf("[DEBUG] Reading workflow permissions for Organization: %s", organizationSlug) + tflog.Debug(ctx, "Reading workflow permissions for Organization", map[string]any{ + "organization_slug": organizationSlug, + }) workflowPerms, _, err := client.Actions.GetDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug) if err != nil { @@ -129,7 +135,9 @@ func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Cont client := meta.(*Owner).v3client organizationSlug := d.Id() - log.Printf("[DEBUG] Resetting workflow permissions to defaults for Organization: %s", organizationSlug) + tflog.Debug(ctx, "Resetting workflow permissions to defaults for Organization", map[string]any{ + "organization_slug": organizationSlug, + }) // Reset to safe defaults workflowPerms := github.DefaultWorkflowPermissionOrganization{ From 9c01d9dc5c1de33f5eca182fe5229a280bfbf11d Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Wed, 7 Jan 2026 21:34:17 +0200 Subject: [PATCH 7/8] Add further Trace, Debug and Info logs Signed-off-by: Timo Sand --- ...tions_organization_workflow_permissions.go | 93 +++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go index 1ff4dffea2..0f20d8f68b 100644 --- a/github/resource_github_actions_organization_workflow_permissions.go +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -57,30 +57,63 @@ func resourceGithubActionsOrganizationWorkflowPermissions() *schema.Resource { } } -func handleEditWorkflowPermissionsError(err error, resp *github.Response) diag.Diagnostics { +func handleEditWorkflowPermissionsError(ctx context.Context, err error, resp *github.Response) diag.Diagnostics { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) { if ghErr.Response.StatusCode == http.StatusConflict { + tflog.Info(ctx, "Detected conflict with workflow permissions", map[string]any{ + "status_code": ghErr.Response.StatusCode, + }) + errorResponse := &GithubActionsOrganizationWorkflowPermissionsErrorResponse{} data, readError := io.ReadAll(resp.Body) if readError == nil && data != nil { unmarshalError := json.Unmarshal(data, errorResponse) if unmarshalError != nil { + tflog.Error(ctx, "Failed to unmarshal error response", map[string]any{ + "error": unmarshalError.Error(), + }) return diag.FromErr(unmarshalError) } + + tflog.Debug(ctx, "Parsed workflow permissions conflict error", map[string]any{ + "message": errorResponse.Message, + "errors": errorResponse.Errors, + "documentation_url": errorResponse.DocumentationURL, + "status": errorResponse.Status, + }) } return diag.FromErr(fmt.Errorf("you are trying to modify a value restricted by the Enterprise's settings.\n Message: %s\n Errors: %s\n Documentation URL: %s\n Status: %s\nerr: %w", errorResponse.Message, errorResponse.Errors, errorResponse.DocumentationURL, errorResponse.Status, err)) } } + + tflog.Trace(ctx, "Returning generic error", map[string]any{ + "error": err.Error(), + }) + return diag.FromErr(err) } func resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + tflog.Trace(ctx, "Entering Create/Update workflow permissions", map[string]any{ + "organization_slug": d.Get("organization_slug").(string), + }) + client := meta.(*Owner).v3client organizationSlug := d.Get("organization_slug").(string) d.SetId(organizationSlug) + if d.IsNewResource() { + tflog.Info(ctx, "Creating organization workflow permissions", map[string]any{ + "organization_slug": organizationSlug, + }) + } else { + tflog.Info(ctx, "Updating organization workflow permissions", map[string]any{ + "organization_slug": organizationSlug, + }) + } + workflowPerms := github.DefaultWorkflowPermissionOrganization{} if v, ok := d.GetOk("default_workflow_permissions"); ok { @@ -91,25 +124,36 @@ func resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate(ctx cont workflowPerms.CanApprovePullRequestReviews = github.Ptr(v.(bool)) } - tflog.Debug(ctx, "Updating workflow permissions for Organization", map[string]any{ + tflog.Debug(ctx, "Calling GitHub API to update workflow permissions", map[string]any{ "organization_slug": organizationSlug, "default_workflow_permissions": workflowPerms.DefaultWorkflowPermissions, "can_approve_pull_request_reviews": workflowPerms.CanApprovePullRequestReviews, }) _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) if err != nil { - return handleEditWorkflowPermissionsError(err, resp) + return handleEditWorkflowPermissionsError(ctx, err, resp) } + tflog.Trace(ctx, "GitHub API call completed successfully", map[string]any{ + "organization_slug": organizationSlug, + }) + // Calling read is necessary as the Update API returns 204 with Empty Body on success + tflog.Trace(ctx, "Exiting Create/Update workflow permissions successfully", map[string]any{ + "organization_slug": organizationSlug, + }) return resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx, d, meta) } func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + tflog.Trace(ctx, "Entering Read workflow permissions", map[string]any{ + "organization_slug": d.Id(), + }) + client := meta.(*Owner).v3client organizationSlug := d.Id() - tflog.Debug(ctx, "Reading workflow permissions for Organization", map[string]any{ + tflog.Debug(ctx, "Calling GitHub API to read workflow permissions", map[string]any{ "organization_slug": organizationSlug, }) @@ -118,6 +162,18 @@ func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Contex return diag.FromErr(err) } + tflog.Debug(ctx, "Retrieved workflow permissions from API", map[string]any{ + "organization_slug": organizationSlug, + "default_workflow_permissions": workflowPerms.DefaultWorkflowPermissions, + "can_approve_pull_request_reviews": workflowPerms.CanApprovePullRequestReviews, + }) + + tflog.Trace(ctx, "Setting state values", map[string]any{ + "organization_slug": organizationSlug, + "default_workflow_permissions": workflowPerms.DefaultWorkflowPermissions, + "can_approve_pull_request_reviews": workflowPerms.CanApprovePullRequestReviews, + }) + if err := d.Set("organization_slug", organizationSlug); err != nil { return diag.FromErr(err) } @@ -128,14 +184,22 @@ func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Contex return diag.FromErr(err) } + tflog.Trace(ctx, "Exiting Read workflow permissions successfully", map[string]any{ + "organization_slug": organizationSlug, + }) + return nil } func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + tflog.Trace(ctx, "Entering Delete workflow permissions", map[string]any{ + "organization_slug": d.Id(), + }) + client := meta.(*Owner).v3client organizationSlug := d.Id() - tflog.Debug(ctx, "Resetting workflow permissions to defaults for Organization", map[string]any{ + tflog.Info(ctx, "Deleting organization workflow permissions (resetting to defaults)", map[string]any{ "organization_slug": organizationSlug, }) @@ -145,10 +209,27 @@ func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Cont CanApprovePullRequestReviews: github.Ptr(false), } + tflog.Debug(ctx, "Using safe default values", map[string]any{ + "default_workflow_permissions": "read", + "can_approve_pull_request_reviews": false, + }) + + tflog.Debug(ctx, "Calling GitHub API to reset workflow permissions", map[string]any{ + "organization_slug": organizationSlug, + }) + _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) if err != nil { - return handleEditWorkflowPermissionsError(err, resp) + return handleEditWorkflowPermissionsError(ctx, err, resp) } + tflog.Trace(ctx, "GitHub API call completed successfully", map[string]any{ + "organization_slug": organizationSlug, + }) + + tflog.Trace(ctx, "Exiting Delete workflow permissions successfully", map[string]any{ + "organization_slug": organizationSlug, + }) + return nil } From ac6dead17954a54047edf7a2334e99d25dbb31b3 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sat, 10 Jan 2026 00:45:48 +0200 Subject: [PATCH 8/8] Address review comments Signed-off-by: Timo Sand --- ...tions_organization_workflow_permissions.go | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go index 0f20d8f68b..c2601a4b0c 100644 --- a/github/resource_github_actions_organization_workflow_permissions.go +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -134,15 +134,10 @@ func resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate(ctx cont return handleEditWorkflowPermissionsError(ctx, err, resp) } - tflog.Trace(ctx, "GitHub API call completed successfully", map[string]any{ - "organization_slug": organizationSlug, - }) - - // Calling read is necessary as the Update API returns 204 with Empty Body on success tflog.Trace(ctx, "Exiting Create/Update workflow permissions successfully", map[string]any{ "organization_slug": organizationSlug, }) - return resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx, d, meta) + return nil } func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { @@ -168,12 +163,6 @@ func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Contex "can_approve_pull_request_reviews": workflowPerms.CanApprovePullRequestReviews, }) - tflog.Trace(ctx, "Setting state values", map[string]any{ - "organization_slug": organizationSlug, - "default_workflow_permissions": workflowPerms.DefaultWorkflowPermissions, - "can_approve_pull_request_reviews": workflowPerms.CanApprovePullRequestReviews, - }) - if err := d.Set("organization_slug", organizationSlug); err != nil { return diag.FromErr(err) } @@ -215,7 +204,8 @@ func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Cont }) tflog.Debug(ctx, "Calling GitHub API to reset workflow permissions", map[string]any{ - "organization_slug": organizationSlug, + "organization_slug": organizationSlug, + "workflow_permissions": workflowPerms, }) _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) @@ -223,10 +213,6 @@ func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Cont return handleEditWorkflowPermissionsError(ctx, err, resp) } - tflog.Trace(ctx, "GitHub API call completed successfully", map[string]any{ - "organization_slug": organizationSlug, - }) - tflog.Trace(ctx, "Exiting Delete workflow permissions successfully", map[string]any{ "organization_slug": organizationSlug, })