diff --git a/github/resource_github_actions_organization_workflow_permissions.go b/github/resource_github_actions_organization_workflow_permissions.go index 174590e116..2cf688d516 100644 --- a/github/resource_github_actions_organization_workflow_permissions.go +++ b/github/resource_github_actions_organization_workflow_permissions.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -type GithubActionsOrganizationWorkflowPermissionsErrorResponse struct { +type githubActionsOrganizationWorkflowPermissionsErrorResponse struct { Message string `json:"message"` Errors string `json:"errors"` DocumentationURL string `json:"documentation_url"` @@ -25,9 +25,9 @@ type GithubActionsOrganizationWorkflowPermissionsErrorResponse struct { func resourceGithubActionsOrganizationWorkflowPermissions() *schema.Resource { return &schema.Resource{ 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, + CreateContext: resourceGithubActionsOrganizationWorkflowPermissionsCreate, ReadContext: resourceGithubActionsOrganizationWorkflowPermissionsRead, - UpdateContext: resourceGithubActionsOrganizationWorkflowPermissionsCreateOrUpdate, + UpdateContext: resourceGithubActionsOrganizationWorkflowPermissionsUpdate, DeleteContext: resourceGithubActionsOrganizationWorkflowPermissionsDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -59,98 +59,78 @@ func resourceGithubActionsOrganizationWorkflowPermissions() *schema.Resource { 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, - }) + if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusConflict { + tflog.Info(ctx, "Detected conflict with workflow permissions", map[string]any{ + "status_code": ghErr.Response.StatusCode, + }) - errorResponse := &GithubActionsOrganizationWorkflowPermissionsErrorResponse{} + errorResponse := &githubActionsOrganizationWorkflowPermissionsErrorResponse{} + if resp != nil && resp.Body != nil { 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{ + if readError != nil { + tflog.Error(ctx, "Failed to read workflow permissions conflict response", map[string]any{ + "error": readError.Error(), + }) + return diag.FromErr(readError) + } + + if len(data) > 0 { + if unmarshalError := json.Unmarshal(data, errorResponse); unmarshalError != nil { + tflog.Error(ctx, "Failed to unmarshal workflow permissions conflict 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(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 { - tflog.Trace(ctx, "Entering Create/Update workflow permissions", map[string]any{ - "organization_slug": d.Get("organization_slug").(string), - }) - - client := meta.(*Owner).v3client +func resourceGithubActionsOrganizationWorkflowPermissionsCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + meta := m.(*Owner) + client := meta.v3client organizationSlug := d.Get("organization_slug").(string) - d.SetId(organizationSlug) + defaultPermissions := d.Get("default_workflow_permissions").(string) + canApprovePRReviews := d.Get("can_approve_pull_request_reviews").(bool) - 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 { - workflowPerms.DefaultWorkflowPermissions = github.Ptr(v.(string)) - } + ctx = tflog.SetField(ctx, "organization_slug", organizationSlug) + tflog.Info(ctx, "Creating workflow permissions") - if v, ok := d.GetOk("can_approve_pull_request_reviews"); ok { - workflowPerms.CanApprovePullRequestReviews = github.Ptr(v.(bool)) + workflowPerms := github.DefaultWorkflowPermissionOrganization{ + DefaultWorkflowPermissions: github.Ptr(defaultPermissions), + CanApprovePullRequestReviews: github.Ptr(canApprovePRReviews), } - 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, + tflog.Debug(ctx, "Calling GitHub API to create workflow permissions", map[string]any{ + "default_workflow_permissions": defaultPermissions, + "can_approve_pull_request_reviews": canApprovePRReviews, }) _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) if err != nil { return handleEditWorkflowPermissionsError(ctx, err, resp) } - tflog.Trace(ctx, "Exiting Create/Update workflow permissions successfully", map[string]any{ - "organization_slug": organizationSlug, - }) - return nil -} + d.SetId(organizationSlug) -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(), - }) + tflog.Trace(ctx, "Created workflow permissions successfully") - client := meta.(*Owner).v3client + return resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx, d, m) +} + +func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + meta := m.(*Owner) + client := meta.v3client organizationSlug := d.Id() - tflog.Debug(ctx, "Calling GitHub API to read workflow permissions", map[string]any{ - "organization_slug": organizationSlug, - }) + + ctx = tflog.SetField(ctx, "id", d.Id()) + ctx = tflog.SetField(ctx, "organization_slug", organizationSlug) + tflog.Info(ctx, "Reading workflow permissions") workflowPerms, _, err := client.Actions.GetDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug) if err != nil { @@ -158,7 +138,6 @@ func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Contex } 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, }) @@ -173,24 +152,53 @@ func resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx context.Contex return diag.FromErr(err) } - tflog.Trace(ctx, "Exiting Read workflow permissions successfully", map[string]any{ - "organization_slug": organizationSlug, - }) + tflog.Trace(ctx, "Read workflow permissions successfully") 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(), +func resourceGithubActionsOrganizationWorkflowPermissionsUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + meta := m.(*Owner) + client := meta.v3client + + organizationSlug := d.Id() + defaultPermissions := d.Get("default_workflow_permissions").(string) + canApprovePRReviews := d.Get("can_approve_pull_request_reviews").(bool) + + ctx = tflog.SetField(ctx, "id", d.Id()) + ctx = tflog.SetField(ctx, "organization_slug", organizationSlug) + tflog.Info(ctx, "Updating workflow permissions") + + workflowPerms := github.DefaultWorkflowPermissionOrganization{ + DefaultWorkflowPermissions: github.Ptr(defaultPermissions), + CanApprovePullRequestReviews: github.Ptr(canApprovePRReviews), + } + + tflog.Debug(ctx, "Calling GitHub API to update workflow permissions", map[string]any{ + "default_workflow_permissions": defaultPermissions, + "can_approve_pull_request_reviews": canApprovePRReviews, }) + _, resp, err := client.Actions.UpdateDefaultWorkflowPermissionsInOrganization(ctx, organizationSlug, workflowPerms) + if err != nil { + return handleEditWorkflowPermissionsError(ctx, err, resp) + } - client := meta.(*Owner).v3client + d.SetId(organizationSlug) + + tflog.Trace(ctx, "Updated workflow permissions successfully") + + return resourceGithubActionsOrganizationWorkflowPermissionsRead(ctx, d, m) +} + +func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + meta := m.(*Owner) + client := meta.v3client organizationSlug := d.Id() - tflog.Info(ctx, "Deleting organization workflow permissions (resetting to defaults)", map[string]any{ - "organization_slug": organizationSlug, - }) + + ctx = tflog.SetField(ctx, "id", d.Id()) + ctx = tflog.SetField(ctx, "organization_slug", organizationSlug) + tflog.Info(ctx, "Deleting organization workflow permissions (resetting to defaults)") // Reset to safe defaults workflowPerms := github.DefaultWorkflowPermissionOrganization{ @@ -198,13 +206,7 @@ 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, "workflow_permissions": workflowPerms, }) @@ -213,9 +215,7 @@ func resourceGithubActionsOrganizationWorkflowPermissionsDelete(ctx context.Cont return handleEditWorkflowPermissionsError(ctx, err, resp) } - tflog.Trace(ctx, "Exiting Delete workflow permissions successfully", map[string]any{ - "organization_slug": organizationSlug, - }) + tflog.Trace(ctx, "Deleted workflow permissions successfully") return nil } diff --git a/github/resource_github_actions_organization_workflow_permissions_test.go b/github/resource_github_actions_organization_workflow_permissions_test.go index 8af9b10f1c..dcaaca7061 100644 --- a/github/resource_github_actions_organization_workflow_permissions_test.go +++ b/github/resource_github_actions_organization_workflow_permissions_test.go @@ -5,24 +5,24 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) 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" + defaultPermission := "write" + canApprovePRReviews := true - default_workflow_permissions = "read" - can_approve_pull_request_reviews = false - } - `, testAccConf.owner) + config := fmt.Sprintf(` +resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" - 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"), - ) + default_workflow_permissions = "%s" + can_approve_pull_request_reviews = %t +} +`, testAccConf.owner, defaultPermission, canApprovePRReviews) resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, @@ -30,75 +30,70 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("id"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("organization_slug"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("default_workflow_permissions"), knownvalue.StringExact(defaultPermission)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("can_approve_pull_request_reviews"), knownvalue.Bool(canApprovePRReviews)), + }, }, }, }) }) 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 - } - `, testAccConf.owner), - - "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 - } - `, testAccConf.owner), - } - - 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"), - ), - } + defaultPermission := "write" + canApprovePRReviews := true + defaultPermissionUpdated := "read" + canApprovePRReviewsUpdated := false + + config := ` +resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" + + default_workflow_permissions = "%s" + can_approve_pull_request_reviews = %t +} +` resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: configs["before"], - Check: checks["before"], + Config: fmt.Sprintf(config, testAccConf.owner, defaultPermission, canApprovePRReviews), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("id"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("organization_slug"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("default_workflow_permissions"), knownvalue.StringExact(defaultPermission)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("can_approve_pull_request_reviews"), knownvalue.Bool(canApprovePRReviews)), + }, }, { - Config: configs["after"], - Check: checks["after"], + Config: fmt.Sprintf(config, testAccConf.owner, defaultPermissionUpdated, canApprovePRReviewsUpdated), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("id"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("organization_slug"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("default_workflow_permissions"), knownvalue.StringExact(defaultPermissionUpdated)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("can_approve_pull_request_reviews"), knownvalue.Bool(canApprovePRReviewsUpdated)), + }, }, }, }) }) 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" + defaultPermission := "write" + canApprovePRReviews := true - default_workflow_permissions = "read" - can_approve_pull_request_reviews = false - } - `, testAccConf.owner) + config := fmt.Sprintf(` +resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" - 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"), - ) + default_workflow_permissions = "%s" + can_approve_pull_request_reviews = %t +} +`, testAccConf.owner, defaultPermission, canApprovePRReviews) resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, @@ -106,7 +101,12 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("id"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("organization_slug"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("default_workflow_permissions"), knownvalue.StringExact(defaultPermission)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("can_approve_pull_request_reviews"), knownvalue.Bool(canApprovePRReviews)), + }, }, { ResourceName: "github_actions_organization_workflow_permissions.test", @@ -119,18 +119,21 @@ 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" +resource "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" - default_workflow_permissions = "write" - can_approve_pull_request_reviews = true - } - `, testAccConf.owner) + 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, + }, { Config: config, Destroy: true, @@ -141,16 +144,10 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { 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 "github_actions_organization_workflow_permissions" "test" { + organization_slug = "%s" +} +`, testAccConf.owner) resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, @@ -158,7 +155,12 @@ func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("id"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("organization_slug"), knownvalue.StringExact(testAccConf.owner)), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("default_workflow_permissions"), knownvalue.StringExact("read")), + statecheck.ExpectKnownValue("github_actions_organization_workflow_permissions.test", tfjsonpath.New("can_approve_pull_request_reviews"), knownvalue.Bool(false)), + }, }, }, })