diff --git a/docs/data-sources/actions_organization_fork_pr_contributor_approval.md b/docs/data-sources/actions_organization_fork_pr_contributor_approval.md new file mode 100644 index 0000000000..1678e0b979 --- /dev/null +++ b/docs/data-sources/actions_organization_fork_pr_contributor_approval.md @@ -0,0 +1,23 @@ +--- +page_title: "github_actions_organization_fork_pr_contributor_approval (Data Source) - GitHub" +description: |- + Read the organization-wide fork PR contributor approval policy +--- + +# github_actions_organization_fork_pr_contributor_approval (Data Source) + +Use this data source to retrieve the current organization-wide fork pull request contributor approval policy. + +## Example Usage + +```terraform +data "github_actions_organization_fork_pr_contributor_approval" "example" {} +``` + +## Argument Reference + +This data source takes no arguments. The organization is determined by the provider configuration. + +## Attributes Reference + +- `approval_policy` - The organization-wide fork PR contributor approval policy currently configured. One of `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. diff --git a/docs/data-sources/actions_repository_fork_pr_contributor_approval.md b/docs/data-sources/actions_repository_fork_pr_contributor_approval.md new file mode 100644 index 0000000000..62967b2b45 --- /dev/null +++ b/docs/data-sources/actions_repository_fork_pr_contributor_approval.md @@ -0,0 +1,25 @@ +--- +page_title: "github_actions_repository_fork_pr_contributor_approval (Data Source) - GitHub" +description: |- + Read the fork PR contributor approval policy for a GitHub repository +--- + +# github_actions_repository_fork_pr_contributor_approval (Data Source) + +Use this data source to retrieve the current fork pull request contributor approval policy configured on a GitHub repository. + +## Example Usage + +```terraform +data "github_actions_repository_fork_pr_contributor_approval" "example" { + repository = "my-repository" +} +``` + +## Argument Reference + +- `repository` - (Required) The GitHub repository. + +## Attributes Reference + +- `approval_policy` - The fork PR contributor approval policy currently configured on the repository. One of `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. diff --git a/docs/resources/actions_organization_fork_pr_contributor_approval.md b/docs/resources/actions_organization_fork_pr_contributor_approval.md new file mode 100644 index 0000000000..404feee8f3 --- /dev/null +++ b/docs/resources/actions_organization_fork_pr_contributor_approval.md @@ -0,0 +1,35 @@ +--- +page_title: "github_actions_organization_fork_pr_contributor_approval (Resource) - GitHub" +description: |- + Manages the organization-wide fork PR contributor approval policy +--- + +# github_actions_organization_fork_pr_contributor_approval (Resource) + +This resource allows you to set the organization-wide fork pull request contributor approval policy. This controls which fork PR contributors need maintainer approval before their workflows can run on any public repository in the organization. You must be an organization owner to use this resource. + +Repositories may override this policy at the repository level (see [`github_actions_repository_fork_pr_contributor_approval`](actions_repository_fork_pr_contributor_approval.md)). Setting the policy at the organization level only establishes the default for repositories that do not have a repository-level override. + +The GitHub API for this setting does not expose an "off" state — the policy is always set to one of the three strictness values. If you remove this resource, the policy is reset to GitHub's documented default (`first_time_contributors`). + +## Example Usage + +```terraform +resource "github_actions_organization_fork_pr_contributor_approval" "test" { + approval_policy = "all_external_contributors" +} +``` + +## Argument Reference + +The following arguments are supported: + +- `approval_policy` - (Required) The organization-wide policy controlling which fork PR contributors need maintainer approval. Possible values are `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. + +## Import + +This resource can be imported using the name of the organization: + +```shell +terraform import github_actions_organization_fork_pr_contributor_approval.test my-organization +``` diff --git a/docs/resources/actions_repository_fork_pr_contributor_approval.md b/docs/resources/actions_repository_fork_pr_contributor_approval.md new file mode 100644 index 0000000000..196df25c2e --- /dev/null +++ b/docs/resources/actions_repository_fork_pr_contributor_approval.md @@ -0,0 +1,42 @@ +--- +page_title: "github_actions_repository_fork_pr_contributor_approval (Resource) - GitHub" +description: |- + Manages the fork PR contributor approval policy for a GitHub repository +--- + +# github_actions_repository_fork_pr_contributor_approval (Resource) + +This resource allows you to set the fork pull request contributor approval policy on a GitHub repository. This controls which fork PR contributors need maintainer approval before their workflows can run on the repository. You must have admin access to a repository to use this resource. + +This setting governs fork PRs from outside contributors. On private repositories, the [`fork-pr-workflows-private-repos`](https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-private-repo-fork-pr-workflow-settings-for-a-repository) org/repo settings control whether fork PR workflows run at all; if fork PR workflows are disabled at that level, configuring `approval_policy` via this resource may return `422 Unprocessable Entity`. + +The GitHub API for this setting does not expose an "off" state — the policy is always one of the three strictness values. On Delete, this resource resets the policy to GitHub's documented default (`first_time_contributors`). + +## Example Usage + +```terraform +resource "github_repository" "example" { + name = "my-repository" + visibility = "public" +} + +resource "github_actions_repository_fork_pr_contributor_approval" "test" { + approval_policy = "all_external_contributors" + repository = github_repository.example.name +} +``` + +## Argument Reference + +The following arguments are supported: + +- `repository` - (Required) The GitHub repository. +- `approval_policy` - (Required) The policy controlling which fork PR contributors need maintainer approval. Possible values are `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. + +## Import + +This resource can be imported using the name of the GitHub repository: + +```shell +terraform import github_actions_repository_fork_pr_contributor_approval.test my-repository +``` diff --git a/examples/data-sources/actions_organization_fork_pr_contributor_approval/example_1.tf b/examples/data-sources/actions_organization_fork_pr_contributor_approval/example_1.tf new file mode 100644 index 0000000000..251a019051 --- /dev/null +++ b/examples/data-sources/actions_organization_fork_pr_contributor_approval/example_1.tf @@ -0,0 +1 @@ +data "github_actions_organization_fork_pr_contributor_approval" "example" {} diff --git a/examples/data-sources/actions_repository_fork_pr_contributor_approval/example_1.tf b/examples/data-sources/actions_repository_fork_pr_contributor_approval/example_1.tf new file mode 100644 index 0000000000..6ed5a429c4 --- /dev/null +++ b/examples/data-sources/actions_repository_fork_pr_contributor_approval/example_1.tf @@ -0,0 +1,3 @@ +data "github_actions_repository_fork_pr_contributor_approval" "example" { + repository = "my-repository" +} diff --git a/examples/resources/actions_organization_fork_pr_contributor_approval/example_1.tf b/examples/resources/actions_organization_fork_pr_contributor_approval/example_1.tf new file mode 100644 index 0000000000..624e54172f --- /dev/null +++ b/examples/resources/actions_organization_fork_pr_contributor_approval/example_1.tf @@ -0,0 +1,3 @@ +resource "github_actions_organization_fork_pr_contributor_approval" "test" { + approval_policy = "all_external_contributors" +} diff --git a/examples/resources/actions_repository_fork_pr_contributor_approval/example_1.tf b/examples/resources/actions_repository_fork_pr_contributor_approval/example_1.tf new file mode 100644 index 0000000000..80c6e01032 --- /dev/null +++ b/examples/resources/actions_repository_fork_pr_contributor_approval/example_1.tf @@ -0,0 +1,9 @@ +resource "github_repository" "example" { + name = "my-repository" + visibility = "public" +} + +resource "github_actions_repository_fork_pr_contributor_approval" "test" { + approval_policy = "all_external_contributors" + repository = github_repository.example.name +} diff --git a/github/data_source_github_actions_organization_fork_pr_contributor_approval.go b/github/data_source_github_actions_organization_fork_pr_contributor_approval.go new file mode 100644 index 0000000000..bf8fb46e4e --- /dev/null +++ b/github/data_source_github_actions_organization_fork_pr_contributor_approval.go @@ -0,0 +1,43 @@ +package github + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubActionsOrganizationForkPRContributorApproval() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceGithubActionsOrganizationForkPRContributorApprovalRead, + + Schema: map[string]*schema.Schema{ + "approval_policy": { + Type: schema.TypeString, + Computed: true, + Description: "The organization-wide fork PR contributor approval policy currently configured. One of 'first_time_contributors_new_to_github', 'first_time_contributors', or 'all_external_contributors'.", + }, + }, + } +} + +func dataSourceGithubActionsOrganizationForkPRContributorApprovalRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + if err := checkOrganization(meta); err != nil { + return diag.FromErr(err) + } + + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + + policy, _, err := client.Actions.GetOrganizationForkPRContributorApprovalPermissions(ctx, orgName) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(orgName) + if err := d.Set("approval_policy", policy.ApprovalPolicy); err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/github/data_source_github_actions_organization_fork_pr_contributor_approval_test.go b/github/data_source_github_actions_organization_fork_pr_contributor_approval_test.go new file mode 100644 index 0000000000..926ca42ea8 --- /dev/null +++ b/github/data_source_github_actions_organization_fork_pr_contributor_approval_test.go @@ -0,0 +1,44 @@ +package github + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccGithubActionsOrganizationForkPRContributorApprovalDataSource(t *testing.T) { + t.Run("read the organization fork PR contributor approval policy", func(t *testing.T) { + approvalPolicy := "all_external_contributors" + + config := ` + resource "github_actions_organization_fork_pr_contributor_approval" "test" { + approval_policy = "all_external_contributors" + } + ` + + config2 := config + ` + data "github_actions_organization_fork_pr_contributor_approval" "test" {} + ` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.github_actions_organization_fork_pr_contributor_approval.test", "approval_policy", approvalPolicy, + ), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc(), + }, + { + Config: config2, + Check: check, + }, + }, + }) + }) +} diff --git a/github/data_source_github_actions_repository_fork_pr_contributor_approval.go b/github/data_source_github_actions_repository_fork_pr_contributor_approval.go new file mode 100644 index 0000000000..6b9479e52d --- /dev/null +++ b/github/data_source_github_actions_repository_fork_pr_contributor_approval.go @@ -0,0 +1,45 @@ +package github + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubActionsRepositoryForkPRContributorApproval() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceGithubActionsRepositoryForkPRContributorApprovalRead, + + Schema: map[string]*schema.Schema{ + "repository": { + Type: schema.TypeString, + Required: true, + Description: "The GitHub repository.", + }, + "approval_policy": { + Type: schema.TypeString, + Computed: true, + Description: "The fork PR contributor approval policy currently configured on the repository. One of 'first_time_contributors_new_to_github', 'first_time_contributors', or 'all_external_contributors'.", + }, + }, + } +} + +func dataSourceGithubActionsRepositoryForkPRContributorApprovalRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + repository := d.Get("repository").(string) + + policy, _, err := client.Actions.GetForkPRContributorApprovalPermissions(ctx, owner, repository) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(repository) + if err := d.Set("approval_policy", policy.ApprovalPolicy); err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/github/data_source_github_actions_repository_fork_pr_contributor_approval_test.go b/github/data_source_github_actions_repository_fork_pr_contributor_approval_test.go new file mode 100644 index 0000000000..e98de60ddd --- /dev/null +++ b/github/data_source_github_actions_repository_fork_pr_contributor_approval_test.go @@ -0,0 +1,61 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccGithubActionsRepositoryForkPRContributorApprovalDataSource(t *testing.T) { + t.Run("read the repository fork PR contributor approval policy", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-fork-pr-approval-ds-%s", testResourcePrefix, randomID) + approvalPolicy := "all_external_contributors" + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%[1]s" + description = "Terraform acceptance tests %[1]s" + topics = ["terraform", "testing"] + visibility = "public" + } + + resource "github_actions_repository_fork_pr_contributor_approval" "test" { + approval_policy = "%[2]s" + repository = github_repository.test.name + } + `, repoName, approvalPolicy) + + config2 := config + ` + data "github_actions_repository_fork_pr_contributor_approval" "test" { + repository = github_actions_repository_fork_pr_contributor_approval.test.repository + } + ` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.github_actions_repository_fork_pr_contributor_approval.test", "approval_policy", approvalPolicy, + ), + resource.TestCheckResourceAttr( + "data.github_actions_repository_fork_pr_contributor_approval.test", "repository", repoName, + ), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, individual, organization) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc(), + }, + { + Config: config2, + Check: check, + }, + }, + }) + }) +} diff --git a/github/provider.go b/github/provider.go index b70c58c9f0..ec370c1178 100644 --- a/github/provider.go +++ b/github/provider.go @@ -132,6 +132,7 @@ func NewProvider() func() *schema.Provider { "github_enterprise_actions_permissions": resourceGithubActionsEnterprisePermissions(), "github_actions_environment_secret": resourceGithubActionsEnvironmentSecret(), "github_actions_environment_variable": resourceGithubActionsEnvironmentVariable(), + "github_actions_organization_fork_pr_contributor_approval": resourceGithubActionsOrganizationForkPRContributorApproval(), "github_actions_organization_oidc_subject_claim_customization_template": resourceGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplate(), "github_actions_organization_permissions": resourceGithubActionsOrganizationPermissions(), "github_actions_organization_secret": resourceGithubActionsOrganizationSecret(), @@ -141,6 +142,7 @@ func NewProvider() func() *schema.Provider { "github_actions_organization_variable_repositories": resourceGithubActionsOrganizationVariableRepositories(), "github_actions_organization_variable_repository": resourceGithubActionsOrganizationVariableRepository(), "github_actions_repository_access_level": resourceGithubActionsRepositoryAccessLevel(), + "github_actions_repository_fork_pr_contributor_approval": resourceGithubActionsRepositoryForkPRContributorApproval(), "github_actions_repository_oidc_subject_claim_customization_template": resourceGithubActionsRepositoryOIDCSubjectClaimCustomizationTemplate(), "github_actions_repository_permissions": resourceGithubActionsRepositoryPermissions(), "github_actions_runner_group": resourceGithubActionsRunnerGroup(), @@ -223,6 +225,7 @@ func NewProvider() func() *schema.Provider { "github_actions_environment_public_key": dataSourceGithubActionsEnvironmentPublicKey(), "github_actions_environment_secrets": dataSourceGithubActionsEnvironmentSecrets(), "github_actions_environment_variables": dataSourceGithubActionsEnvironmentVariables(), + "github_actions_organization_fork_pr_contributor_approval": dataSourceGithubActionsOrganizationForkPRContributorApproval(), "github_actions_organization_oidc_subject_claim_customization_template": dataSourceGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplate(), "github_actions_organization_public_key": dataSourceGithubActionsOrganizationPublicKey(), "github_actions_organization_registration_token": dataSourceGithubActionsOrganizationRegistrationToken(), @@ -230,6 +233,7 @@ func NewProvider() func() *schema.Provider { "github_actions_organization_variables": dataSourceGithubActionsOrganizationVariables(), "github_actions_public_key": dataSourceGithubActionsPublicKey(), "github_actions_registration_token": dataSourceGithubActionsRegistrationToken(), + "github_actions_repository_fork_pr_contributor_approval": dataSourceGithubActionsRepositoryForkPRContributorApproval(), "github_actions_repository_oidc_subject_claim_customization_template": dataSourceGithubActionsRepositoryOIDCSubjectClaimCustomizationTemplate(), "github_actions_secrets": dataSourceGithubActionsSecrets(), "github_actions_variables": dataSourceGithubActionsVariables(), diff --git a/github/resource_github_actions_organization_fork_pr_contributor_approval.go b/github/resource_github_actions_organization_fork_pr_contributor_approval.go new file mode 100644 index 0000000000..20f18f6ae2 --- /dev/null +++ b/github/resource_github_actions_organization_fork_pr_contributor_approval.go @@ -0,0 +1,96 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v86/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" +) + +func resourceGithubActionsOrganizationForkPRContributorApproval() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceGithubActionsOrganizationForkPRContributorApprovalCreateOrUpdate, + ReadContext: resourceGithubActionsOrganizationForkPRContributorApprovalRead, + UpdateContext: resourceGithubActionsOrganizationForkPRContributorApprovalCreateOrUpdate, + DeleteContext: resourceGithubActionsOrganizationForkPRContributorApprovalDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "approval_policy": { + Type: schema.TypeString, + Required: true, + Description: "The organization-wide policy controlling which fork PR contributors need maintainer approval before their workflows can run. Possible values are 'first_time_contributors_new_to_github', 'first_time_contributors', or 'all_external_contributors'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "first_time_contributors_new_to_github", + "first_time_contributors", + "all_external_contributors", + }, false)), + }, + }, + } +} + +func resourceGithubActionsOrganizationForkPRContributorApprovalCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + if err := checkOrganization(meta); err != nil { + return diag.FromErr(err) + } + + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + + policy := github.ContributorApprovalPermissions{ + ApprovalPolicy: d.Get("approval_policy").(string), + } + + if _, err := client.Actions.UpdateOrganizationForkPRContributorApprovalPermissions(ctx, orgName, policy); err != nil { + return diag.FromErr(err) + } + + d.SetId(orgName) + return resourceGithubActionsOrganizationForkPRContributorApprovalRead(ctx, d, meta) +} + +func resourceGithubActionsOrganizationForkPRContributorApprovalRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + if err := checkOrganization(meta); err != nil { + return diag.FromErr(err) + } + + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + + policy, _, err := client.Actions.GetOrganizationForkPRContributorApprovalPermissions(ctx, orgName) + if err != nil { + return diag.FromErr(err) + } + + if err := d.Set("approval_policy", policy.ApprovalPolicy); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceGithubActionsOrganizationForkPRContributorApprovalDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + if err := checkOrganization(meta); err != nil { + return diag.FromErr(err) + } + + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + + // The API has no "off" state for this policy. Reset to the GitHub-documented + // default (first_time_contributors) on Delete so the resource leaves no + // residual non-default state behind. + policy := github.ContributorApprovalPermissions{ + ApprovalPolicy: "first_time_contributors", + } + if _, err := client.Actions.UpdateOrganizationForkPRContributorApprovalPermissions(ctx, orgName, policy); err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/github/resource_github_actions_organization_fork_pr_contributor_approval_test.go b/github/resource_github_actions_organization_fork_pr_contributor_approval_test.go new file mode 100644 index 0000000000..13ce508c24 --- /dev/null +++ b/github/resource_github_actions_organization_fork_pr_contributor_approval_test.go @@ -0,0 +1,49 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccGithubActionsOrganizationForkPRContributorApproval(t *testing.T) { + policies := []string{ + "first_time_contributors_new_to_github", + "first_time_contributors", + "all_external_contributors", + } + + for _, policy := range policies { + t.Run(fmt.Sprintf("test setting org approval_policy to %s", policy), func(t *testing.T) { + approvalPolicy := policy + config := fmt.Sprintf(` + resource "github_actions_organization_fork_pr_contributor_approval" "test" { + approval_policy = "%s" + } + `, approvalPolicy) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_organization_fork_pr_contributor_approval.test", "approval_policy", approvalPolicy, + ), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + { + ResourceName: "github_actions_organization_fork_pr_contributor_approval.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + } +} diff --git a/github/resource_github_actions_repository_fork_pr_contributor_approval.go b/github/resource_github_actions_repository_fork_pr_contributor_approval.go new file mode 100644 index 0000000000..2ad56b9acd --- /dev/null +++ b/github/resource_github_actions_repository_fork_pr_contributor_approval.go @@ -0,0 +1,96 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v86/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" +) + +func resourceGithubActionsRepositoryForkPRContributorApproval() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceGithubActionsRepositoryForkPRContributorApprovalCreateOrUpdate, + ReadContext: resourceGithubActionsRepositoryForkPRContributorApprovalRead, + UpdateContext: resourceGithubActionsRepositoryForkPRContributorApprovalCreateOrUpdate, + DeleteContext: resourceGithubActionsRepositoryForkPRContributorApprovalDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "approval_policy": { + Type: schema.TypeString, + Required: true, + Description: "The policy controlling which fork PR contributors need maintainer approval before their workflows can run. Possible values are 'first_time_contributors_new_to_github', 'first_time_contributors', or 'all_external_contributors'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "first_time_contributors_new_to_github", + "first_time_contributors", + "all_external_contributors", + }, false)), + }, + "repository": { + Type: schema.TypeString, + Required: true, + Description: "The GitHub repository.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 100)), + }, + }, + } +} + +func resourceGithubActionsRepositoryForkPRContributorApprovalCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + repoName := d.Get("repository").(string) + + policy := github.ContributorApprovalPermissions{ + ApprovalPolicy: d.Get("approval_policy").(string), + } + + if _, err := client.Actions.UpdateForkPRContributorApprovalPermissions(ctx, owner, repoName, policy); err != nil { + return diag.FromErr(err) + } + + d.SetId(repoName) + return resourceGithubActionsRepositoryForkPRContributorApprovalRead(ctx, d, meta) +} + +func resourceGithubActionsRepositoryForkPRContributorApprovalRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + repoName := d.Id() + + policy, _, err := client.Actions.GetForkPRContributorApprovalPermissions(ctx, owner, repoName) + if err != nil { + return diag.FromErr(err) + } + + if err := d.Set("repository", repoName); err != nil { + return diag.FromErr(err) + } + if err := d.Set("approval_policy", policy.ApprovalPolicy); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceGithubActionsRepositoryForkPRContributorApprovalDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + repoName := d.Id() + + // The API has no "off" state for this policy. Reset to the GitHub-documented + // default (first_time_contributors) on Delete so the resource leaves no + // residual non-default state behind. + policy := github.ContributorApprovalPermissions{ + ApprovalPolicy: "first_time_contributors", + } + if _, err := client.Actions.UpdateForkPRContributorApprovalPermissions(ctx, owner, repoName, policy); err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/github/resource_github_actions_repository_fork_pr_contributor_approval_test.go b/github/resource_github_actions_repository_fork_pr_contributor_approval_test.go new file mode 100644 index 0000000000..771c090d29 --- /dev/null +++ b/github/resource_github_actions_repository_fork_pr_contributor_approval_test.go @@ -0,0 +1,63 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccGithubActionsRepositoryForkPRContributorApproval(t *testing.T) { + policies := []string{ + "first_time_contributors_new_to_github", + "first_time_contributors", + "all_external_contributors", + } + + for _, policy := range policies { + t.Run(fmt.Sprintf("test setting approval_policy to %s", policy), func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-fork-pr-approval-%s", testResourcePrefix, randomID) + approvalPolicy := policy + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%[1]s" + description = "Terraform acceptance tests %[1]s" + topics = ["terraform", "testing"] + visibility = "public" + } + + resource "github_actions_repository_fork_pr_contributor_approval" "test" { + approval_policy = "%[2]s" + repository = github_repository.test.name + } + `, repoName, approvalPolicy) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_repository_fork_pr_contributor_approval.test", "approval_policy", approvalPolicy, + ), + resource.TestCheckResourceAttr( + "github_actions_repository_fork_pr_contributor_approval.test", "repository", repoName, + ), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, individual, organization) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + { + ResourceName: "github_actions_repository_fork_pr_contributor_approval.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + } +} diff --git a/templates/data-sources/actions_organization_fork_pr_contributor_approval.md.tmpl b/templates/data-sources/actions_organization_fork_pr_contributor_approval.md.tmpl new file mode 100644 index 0000000000..9ec6ceba24 --- /dev/null +++ b/templates/data-sources/actions_organization_fork_pr_contributor_approval.md.tmpl @@ -0,0 +1,21 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Read the organization-wide fork PR contributor approval policy +--- + +# {{.Name}} ({{.Type}}) + +Use this data source to retrieve the current organization-wide fork pull request contributor approval policy. + +## Example Usage + +{{ tffile "examples/data-sources/actions_organization_fork_pr_contributor_approval/example_1.tf" }} + +## Argument Reference + +This data source takes no arguments. The organization is determined by the provider configuration. + +## Attributes Reference + +- `approval_policy` - The organization-wide fork PR contributor approval policy currently configured. One of `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. diff --git a/templates/data-sources/actions_repository_fork_pr_contributor_approval.md.tmpl b/templates/data-sources/actions_repository_fork_pr_contributor_approval.md.tmpl new file mode 100644 index 0000000000..687533bf07 --- /dev/null +++ b/templates/data-sources/actions_repository_fork_pr_contributor_approval.md.tmpl @@ -0,0 +1,21 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Read the fork PR contributor approval policy for a GitHub repository +--- + +# {{.Name}} ({{.Type}}) + +Use this data source to retrieve the current fork pull request contributor approval policy configured on a GitHub repository. + +## Example Usage + +{{ tffile "examples/data-sources/actions_repository_fork_pr_contributor_approval/example_1.tf" }} + +## Argument Reference + +- `repository` - (Required) The GitHub repository. + +## Attributes Reference + +- `approval_policy` - The fork PR contributor approval policy currently configured on the repository. One of `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. diff --git a/templates/resources/actions_organization_fork_pr_contributor_approval.md.tmpl b/templates/resources/actions_organization_fork_pr_contributor_approval.md.tmpl new file mode 100644 index 0000000000..74718d789e --- /dev/null +++ b/templates/resources/actions_organization_fork_pr_contributor_approval.md.tmpl @@ -0,0 +1,31 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Manages the organization-wide fork PR contributor approval policy +--- + +# {{.Name}} ({{.Type}}) + +This resource allows you to set the organization-wide fork pull request contributor approval policy. This controls which fork PR contributors need maintainer approval before their workflows can run on any public repository in the organization. You must be an organization owner to use this resource. + +Repositories may override this policy at the repository level (see [`github_actions_repository_fork_pr_contributor_approval`](actions_repository_fork_pr_contributor_approval.md)). Setting the policy at the organization level only establishes the default for repositories that do not have a repository-level override. + +The GitHub API for this setting does not expose an "off" state — the policy is always set to one of the three strictness values. If you remove this resource, the policy is reset to GitHub's documented default (`first_time_contributors`). + +## Example Usage + +{{ tffile "examples/resources/actions_organization_fork_pr_contributor_approval/example_1.tf" }} + +## Argument Reference + +The following arguments are supported: + +- `approval_policy` - (Required) The organization-wide policy controlling which fork PR contributors need maintainer approval. Possible values are `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. + +## Import + +This resource can be imported using the name of the organization: + +```shell +terraform import github_actions_organization_fork_pr_contributor_approval.test my-organization +``` diff --git a/templates/resources/actions_repository_fork_pr_contributor_approval.md.tmpl b/templates/resources/actions_repository_fork_pr_contributor_approval.md.tmpl new file mode 100644 index 0000000000..2712f90ca4 --- /dev/null +++ b/templates/resources/actions_repository_fork_pr_contributor_approval.md.tmpl @@ -0,0 +1,32 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Manages the fork PR contributor approval policy for a GitHub repository +--- + +# {{.Name}} ({{.Type}}) + +This resource allows you to set the fork pull request contributor approval policy on a GitHub repository. This controls which fork PR contributors need maintainer approval before their workflows can run on the repository. You must have admin access to a repository to use this resource. + +This setting governs fork PRs from outside contributors. On private repositories, the [`fork-pr-workflows-private-repos`](https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-private-repo-fork-pr-workflow-settings-for-a-repository) org/repo settings control whether fork PR workflows run at all; if fork PR workflows are disabled at that level, configuring `approval_policy` via this resource may return `422 Unprocessable Entity`. + +The GitHub API for this setting does not expose an "off" state — the policy is always one of the three strictness values. On Delete, this resource resets the policy to GitHub's documented default (`first_time_contributors`). + +## Example Usage + +{{ tffile "examples/resources/actions_repository_fork_pr_contributor_approval/example_1.tf" }} + +## Argument Reference + +The following arguments are supported: + +- `repository` - (Required) The GitHub repository. +- `approval_policy` - (Required) The policy controlling which fork PR contributors need maintainer approval. Possible values are `first_time_contributors_new_to_github`, `first_time_contributors`, or `all_external_contributors`. + +## Import + +This resource can be imported using the name of the GitHub repository: + +```shell +terraform import github_actions_repository_fork_pr_contributor_approval.test my-repository +```