diff --git a/github/resource_github_actions_organization_permissions.go b/github/resource_github_actions_organization_permissions.go index bcc685022f..c8e6e5236a 100644 --- a/github/resource_github_actions_organization_permissions.go +++ b/github/resource_github_actions_organization_permissions.go @@ -12,9 +12,9 @@ import ( func resourceGithubActionsOrganizationPermissions() *schema.Resource { return &schema.Resource{ - Create: resourceGithubActionsOrganizationPermissionsCreateOrUpdate, + Create: resourceGithubActionsOrganizationPermissionsCreate, Read: resourceGithubActionsOrganizationPermissionsRead, - Update: resourceGithubActionsOrganizationPermissionsCreateOrUpdate, + Update: resourceGithubActionsOrganizationPermissionsUpdate, Delete: resourceGithubActionsOrganizationPermissionsDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -137,13 +137,10 @@ func resourceGithubActionsEnabledRepositoriesObject(d *schema.ResourceData) ([]i return enabled, nil } -func resourceGithubActionsOrganizationPermissionsCreateOrUpdate(d *schema.ResourceData, meta any) error { +func resourceGithubActionsOrganizationPermissionsCreate(d *schema.ResourceData, meta any) error { client := meta.(*Owner).v3client orgName := meta.(*Owner).name ctx := context.Background() - if !d.IsNewResource() { - ctx = context.WithValue(ctx, ctxId, d.Id()) - } err := checkOrganization(meta) if err != nil { @@ -158,8 +155,20 @@ func resourceGithubActionsOrganizationPermissionsCreateOrUpdate(d *schema.Resour EnabledRepositories: &enabledRepositories, } - if v, ok := d.GetOk("sha_pinning_required"); ok { - actionsPermissions.SHAPinningRequired = new(v.(bool)) + // Use `GetOkExists` for `sha_pinning_required` to detect explicit + // `false` values. + // + // The `sha_pinning_required` is an Optional and Computed boolean. + // + // When a user writes `sha_pinning_required = false`, + // `GetOk` returns `(false, false)`. The second `false` argument + // means "not set", which is indistinguishable from the user omitting + // the field entirely. So the explicit false was silently ignored. + // + // `GetOkExists` returns `(false, true)`. + // The `true` value correctly indicates the user did want to set it. + if v, ok := d.GetOkExists("sha_pinning_required"); ok { //nolint:staticcheck + actionsPermissions.SHAPinningRequired = github.Ptr(v.(bool)) } _, _, err = client.Actions.UpdateActionsPermissions(ctx, @@ -201,6 +210,68 @@ func resourceGithubActionsOrganizationPermissionsCreateOrUpdate(d *schema.Resour return resourceGithubActionsOrganizationPermissionsRead(d, meta) } +func resourceGithubActionsOrganizationPermissionsUpdate(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + err := checkOrganization(meta) + if err != nil { + return err + } + + allowedActions := d.Get("allowed_actions").(string) + enabledRepositories := d.Get("enabled_repositories").(string) + + actionsPermissions := github.ActionsPermissions{ + AllowedActions: &allowedActions, + EnabledRepositories: &enabledRepositories, + } + + // Use `HasChange` + `Get` for `sha_pinning_required` to send the + // value only when it changes. + if d.HasChange("sha_pinning_required") { + actionsPermissions.SHAPinningRequired = github.Ptr(d.Get("sha_pinning_required").(bool)) + } + + _, _, err = client.Actions.UpdateActionsPermissions(ctx, + orgName, + actionsPermissions) + if err != nil { + return err + } + + if allowedActions == "selected" { + actionsAllowedData := resourceGithubActionsOrganizationAllowedObject(d) + if actionsAllowedData != nil { + log.Printf("[DEBUG] The allowedActions variable is set.") + _, _, err = client.Actions.UpdateActionsAllowed(ctx, + orgName, + *actionsAllowedData) + if err != nil { + return err + } + } else { + log.Printf("[DEBUG] The allowedActions variable is not set, skipping.") + } + } + + if enabledRepositories == "selected" { + enabledReposData, err := resourceGithubActionsEnabledRepositoriesObject(d) + if err != nil { + return err + } + _, err = client.Actions.SetEnabledReposInOrg(ctx, + orgName, + enabledReposData) + if err != nil { + return err + } + } + + return resourceGithubActionsOrganizationPermissionsRead(d, meta) +} + func resourceGithubActionsOrganizationPermissionsRead(d *schema.ResourceData, meta any) error { client := meta.(*Owner).v3client ctx := context.Background() diff --git a/github/resource_github_actions_organization_permissions_test.go b/github/resource_github_actions_organization_permissions_test.go index 8aa3b9516e..85362cd51d 100644 --- a/github/resource_github_actions_organization_permissions_test.go +++ b/github/resource_github_actions_organization_permissions_test.go @@ -183,6 +183,49 @@ func TestAccGithubActionsOrganizationPermissions(t *testing.T) { }) }) + t.Run("test setting sha_pinning_required to true then updating to false", func(t *testing.T) { + enabledRepositories := "all" + + configTrue := fmt.Sprintf(` + resource "github_actions_organization_permissions" "test" { + allowed_actions = "all" + enabled_repositories = "%s" + sha_pinning_required = true + } + `, enabledRepositories) + + configFalse := fmt.Sprintf(` + resource "github_actions_organization_permissions" "test" { + allowed_actions = "all" + enabled_repositories = "%s" + sha_pinning_required = false + } + `, enabledRepositories) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: configTrue, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_organization_permissions.test", "sha_pinning_required", "true", + ), + ), + }, + { + Config: configFalse, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_organization_permissions.test", "sha_pinning_required", "false", + ), + ), + }, + }, + }) + }) + t.Run("test setting of organization enabled repositories", func(t *testing.T) { allowedActions := "all" enabledRepositories := "selected" diff --git a/github/resource_github_actions_repository_permissions.go b/github/resource_github_actions_repository_permissions.go index 3dfddc4092..92078f3494 100644 --- a/github/resource_github_actions_repository_permissions.go +++ b/github/resource_github_actions_repository_permissions.go @@ -11,9 +11,9 @@ import ( func resourceGithubActionsRepositoryPermissions() *schema.Resource { return &schema.Resource{ - Create: resourceGithubActionsRepositoryPermissionsCreateOrUpdate, + Create: resourceGithubActionsRepositoryPermissionsCreate, Read: resourceGithubActionsRepositoryPermissionsRead, - Update: resourceGithubActionsRepositoryPermissionsCreateOrUpdate, + Update: resourceGithubActionsRepositoryPermissionsUpdate, Delete: resourceGithubActionsRepositoryPermissionsDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -108,15 +108,12 @@ func resourceGithubActionsRepositoryAllowedObject(d *schema.ResourceData) *githu return allowed } -func resourceGithubActionsRepositoryPermissionsCreateOrUpdate(d *schema.ResourceData, meta any) error { +func resourceGithubActionsRepositoryPermissionsCreate(d *schema.ResourceData, meta any) error { client := meta.(*Owner).v3client owner := meta.(*Owner).name repoName := d.Get("repository").(string) ctx := context.Background() - if !d.IsNewResource() { - ctx = context.WithValue(ctx, ctxId, d.Id()) - } allowedActions := d.Get("allowed_actions").(string) enabled := d.Get("enabled").(bool) @@ -126,13 +123,25 @@ func resourceGithubActionsRepositoryPermissionsCreateOrUpdate(d *schema.Resource Enabled: &enabled, } - // Only specify `allowed_actions` if actions are enabled + // Only specify `allowed_actions` if actions are enabled. if enabled { repoActionPermissions.AllowedActions = &allowedActions } - if v, ok := d.GetOk("sha_pinning_required"); ok { - repoActionPermissions.SHAPinningRequired = new(v.(bool)) + // Use `GetOkExists` for `sha_pinning_required` to detect explicit + // `false` values. + // + // The `sha_pinning_required` is an Optional and Computed boolean. + // + // When a user writes `sha_pinning_required = false`, + // `GetOk` returns `(false, false)`. The second `false` argument + // means "not set", which is indistinguishable from the user omitting + // the field entirely. So the explicit false was silently ignored. + // + // `GetOkExists` returns `(false, true)`. + // The `true` value correctly indicates the user did want to set it. + if v, ok := d.GetOkExists("sha_pinning_required"); ok { //nolint:staticcheck + repoActionPermissions.SHAPinningRequired = github.Ptr(v.(bool)) } _, _, err := client.Repositories.UpdateActionsPermissions(ctx, @@ -164,6 +173,60 @@ func resourceGithubActionsRepositoryPermissionsCreateOrUpdate(d *schema.Resource return resourceGithubActionsRepositoryPermissionsRead(d, meta) } +func resourceGithubActionsRepositoryPermissionsUpdate(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + + owner := meta.(*Owner).name + repoName := d.Get("repository").(string) + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + allowedActions := d.Get("allowed_actions").(string) + enabled := d.Get("enabled").(bool) + log.Printf("[DEBUG] Actions enabled: %t", enabled) + + repoActionPermissions := github.ActionsPermissionsRepository{ + Enabled: &enabled, + } + + // Only specify `allowed_actions` if actions are enabled. + if enabled { + repoActionPermissions.AllowedActions = &allowedActions + } + + // Use `HasChange` + `Get` for `sha_pinning_required` to send the + // value only when it changes. + if d.HasChange("sha_pinning_required") { + repoActionPermissions.SHAPinningRequired = github.Ptr(d.Get("sha_pinning_required").(bool)) + } + + _, _, err := client.Repositories.UpdateActionsPermissions(ctx, + owner, + repoName, + repoActionPermissions, + ) + if err != nil { + return err + } + + if allowedActions == "selected" { + actionsAllowedData := resourceGithubActionsRepositoryAllowedObject(d) + if actionsAllowedData != nil { + log.Printf("[DEBUG] The allowedActions variable is set.") + _, _, err = client.Repositories.EditActionsAllowed(ctx, + owner, + repoName, + *actionsAllowedData) + if err != nil { + return err + } + } else { + log.Printf("[DEBUG] The allowedActions variable is not set, skipping.") + } + } + + return resourceGithubActionsRepositoryPermissionsRead(d, meta) +} + func resourceGithubActionsRepositoryPermissionsRead(d *schema.ResourceData, meta any) error { client := meta.(*Owner).v3client diff --git a/github/resource_github_actions_repository_permissions_test.go b/github/resource_github_actions_repository_permissions_test.go index a64352a57c..828c528f58 100644 --- a/github/resource_github_actions_repository_permissions_test.go +++ b/github/resource_github_actions_repository_permissions_test.go @@ -185,6 +185,62 @@ func TestAccGithubActionsRepositoryPermissions(t *testing.T) { }) }) + t.Run("test setting sha_pinning_required to true then updating to false", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-act-perms-%s", testResourcePrefix, randomID) + + configTrue := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%[1]s" + description = "Terraform acceptance tests %[1]s" + topics = ["terraform", "testing"] + } + + resource "github_actions_repository_permissions" "test" { + allowed_actions = "all" + repository = github_repository.test.name + sha_pinning_required = true + } + `, repoName) + + configFalse := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%[1]s" + description = "Terraform acceptance tests %[1]s" + topics = ["terraform", "testing"] + } + + resource "github_actions_repository_permissions" "test" { + allowed_actions = "all" + repository = github_repository.test.name + sha_pinning_required = false + } + `, repoName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: configTrue, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_repository_permissions.test", "sha_pinning_required", "true", + ), + ), + }, + { + Config: configFalse, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_repository_permissions.test", "sha_pinning_required", "false", + ), + ), + }, + }, + }) + }) + t.Run("test disabling actions on a repository", func(t *testing.T) { actionsEnabled := false randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)