-
Notifications
You must be signed in to change notification settings - Fork 1k
feat: Add github_repository_pull_request_creation_policy resource #3349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3004840
efeb9d4
79da10f
20790ae
b2f15e8
a57f130
890cab5
f9a3ed3
33377d3
97d8104
69131f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| --- | ||
| page_title: "github_repository_pull_request_creation_policy (Resource) - GitHub" | ||
| description: |- | ||
| Manages the pull request creation policy for a repository | ||
| --- | ||
|
|
||
| # github_repository_pull_request_creation_policy (Resource) | ||
|
|
||
| This resource allows you to manage the pull request creation policy for a repository. The policy controls who is allowed to create pull requests in the repository. | ||
|
|
||
| Destroying this resource does not delete anything on GitHub; it resets the repository's pull request creation policy to `all`. | ||
|
|
||
| ## Example Usage | ||
|
|
||
| ```terraform | ||
| resource "github_repository" "example" { | ||
| name = "example-repo" | ||
| visibility = "private" | ||
| } | ||
|
|
||
| resource "github_repository_pull_request_creation_policy" "example" { | ||
| repository = github_repository.example.name | ||
| policy = "collaborators_only" | ||
| } | ||
| ``` | ||
|
|
||
| ## Argument Reference | ||
|
|
||
| The following arguments are supported: | ||
|
|
||
| - `repository` - (Required) The name of the GitHub repository. Renaming the repository is supported without recreating this resource. | ||
|
|
||
| - `policy` - (Required) Controls who can create pull requests in the repository. Can be `all` or `collaborators_only`. | ||
|
|
||
| ## Attribute Reference | ||
|
|
||
| In addition to the above arguments, the following attributes are exported: | ||
|
|
||
| - `repository_id` - The numeric ID of the GitHub repository. | ||
|
|
||
| ## Import | ||
|
|
||
| The pull request creation policy can be imported using the repository name. | ||
|
|
||
| ```shell | ||
| terraform import github_repository_pull_request_creation_policy.example my-repo | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| resource "github_repository" "example" { | ||
| name = "example-repo" | ||
| visibility = "private" | ||
| } | ||
|
|
||
| resource "github_repository_pull_request_creation_policy" "example" { | ||
| repository = github_repository.example.name | ||
| policy = "collaborators_only" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-log/tflog" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" | ||
| ) | ||
|
|
||
| func resourceGithubRepositoryPullRequestCreationPolicy() *schema.Resource { | ||
| return &schema.Resource{ | ||
| Description: "Manages the pull request creation policy for a repository.", | ||
| CreateContext: resourceGithubRepositoryPullRequestCreationPolicyCreate, | ||
| ReadContext: resourceGithubRepositoryPullRequestCreationPolicyRead, | ||
| UpdateContext: resourceGithubRepositoryPullRequestCreationPolicyUpdate, | ||
| DeleteContext: resourceGithubRepositoryPullRequestCreationPolicyDelete, | ||
| Importer: &schema.ResourceImporter{ | ||
| StateContext: schema.ImportStatePassthroughContext, | ||
| }, | ||
| CustomizeDiff: diffRepository, | ||
|
|
||
| Schema: map[string]*schema.Schema{ | ||
| "repository": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| Description: "The name of the GitHub repository.", | ||
| }, | ||
| "repository_id": { | ||
| Type: schema.TypeInt, | ||
| Computed: true, | ||
| Description: "The numeric ID of the GitHub repository.", | ||
| }, | ||
| "policy": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| Description: "Controls who can create pull requests for the repository. Can be `all` or `collaborators_only`.", | ||
| ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"all", "collaborators_only"}, false)), | ||
| }, | ||
| }, | ||
| } | ||
|
Comment on lines
+24
to
+42
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in 97d8104. Added the computed |
||
| } | ||
|
|
||
| func resourceGithubRepositoryPullRequestCreationPolicyCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| owner := meta.(*Owner).name | ||
| repoName := d.Get("repository").(string) | ||
| policy := d.Get("policy").(string) | ||
|
|
||
| nodeID, databaseID, err := getRepositoryNodeAndDatabaseID(ctx, owner, repoName, meta) | ||
| if err != nil { | ||
| return diag.Errorf("error resolving repository ID for %s: %s", repoName, err) | ||
| } | ||
|
|
||
| if err := updateRepositoryPullRequestCreationPolicy(ctx, nodeID, policy, meta); err != nil { | ||
| return diag.Errorf("error setting pull request creation policy for %s: %s", repoName, err) | ||
| } | ||
|
|
||
| d.SetId(repoName) | ||
| if err := d.Set("repository_id", databaseID); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubRepositoryPullRequestCreationPolicyRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| owner := meta.(*Owner).name | ||
| repoName := d.Id() | ||
|
|
||
| policy, databaseID, err := getRepositoryPullRequestCreationPolicy(ctx, owner, repoName, meta) | ||
| if err != nil { | ||
| if isRepositoryNotFoundError(err) { | ||
| tflog.Info(ctx, "Repository no longer exists, removing pull request creation policy from state", map[string]any{ | ||
| "owner": owner, | ||
| "repository": repoName, | ||
| }) | ||
| d.SetId("") | ||
| return nil | ||
| } | ||
| return diag.Errorf("error reading pull request creation policy for %s/%s: %s", owner, repoName, err) | ||
| } | ||
|
Comment on lines
+72
to
+82
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please differentiate between an actual error and for example the "repo doesn't exist anymore" case where this resource can be removed from state directly
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in b2f15e8: |
||
|
|
||
| if err := d.Set("policy", policy); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| if err := d.Set("repository", repoName); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| if err := d.Set("repository_id", databaseID); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubRepositoryPullRequestCreationPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| owner := meta.(*Owner).name | ||
| repoName := d.Get("repository").(string) | ||
| policy := d.Get("policy").(string) | ||
|
|
||
| nodeID, databaseID, err := getRepositoryNodeAndDatabaseID(ctx, owner, repoName, meta) | ||
| if err != nil { | ||
| return diag.Errorf("error resolving repository ID for %s: %s", repoName, err) | ||
| } | ||
|
|
||
| if err := updateRepositoryPullRequestCreationPolicy(ctx, nodeID, policy, meta); err != nil { | ||
| return diag.Errorf("error updating pull request creation policy for %s: %s", repoName, err) | ||
| } | ||
|
|
||
| d.SetId(repoName) | ||
| if err := d.Set("repository_id", databaseID); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubRepositoryPullRequestCreationPolicyDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| repoName := d.Id() | ||
|
|
||
| nodeID, err := getRepositoryID(repoName, meta) | ||
| if err != nil { | ||
| if isRepositoryNotFoundError(err) { | ||
| tflog.Info(ctx, "Repository no longer exists, nothing to reset for the pull request creation policy", map[string]any{ | ||
| "repository": repoName, | ||
| }) | ||
| return nil | ||
| } | ||
| return diag.Errorf("error resolving repository node ID for %s: %s", repoName, err) | ||
| } | ||
|
|
||
| if err := updateRepositoryPullRequestCreationPolicy(ctx, nodeID, "all", meta); err != nil { | ||
| return diag.Errorf("error resetting pull request creation policy for %s: %s", repoName, err) | ||
| } | ||
|
Comment on lines
+123
to
+135
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check if it's an actual error before returning an error. Is the repo gone? Not an actual error, delete resource from state and be happy.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 890cab5: |
||
|
|
||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-testing/helper/acctest" | ||
| "github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
| "github.com/hashicorp/terraform-plugin-testing/knownvalue" | ||
| "github.com/hashicorp/terraform-plugin-testing/plancheck" | ||
| "github.com/hashicorp/terraform-plugin-testing/statecheck" | ||
| "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" | ||
| ) | ||
|
|
||
| func TestAccGithubRepositoryPullRequestCreationPolicy(t *testing.T) { | ||
| t.Run("sets policy without error", func(t *testing.T) { | ||
| randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) | ||
| repoName := fmt.Sprintf("%srepo-pr-policy-%s", testResourcePrefix, randomID) | ||
| initial := "collaborators_only" | ||
| updated := "all" | ||
|
|
||
| config := ` | ||
| resource "github_repository" "test" { | ||
| name = "%s" | ||
| visibility = "private" | ||
| auto_init = true | ||
| } | ||
|
|
||
| resource "github_repository_pull_request_creation_policy" "test" { | ||
| repository = github_repository.test.name | ||
| policy = "%s" | ||
| } | ||
| ` | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { skipUnauthenticated(t) }, | ||
| ProviderFactories: providerFactories, | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| Config: fmt.Sprintf(config, repoName, initial), | ||
| ConfigStateChecks: []statecheck.StateCheck{ | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("policy"), | ||
| knownvalue.StringExact(initial), | ||
| ), | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("repository_id"), | ||
| knownvalue.NotNull(), | ||
| ), | ||
| }, | ||
| }, | ||
| { | ||
| Config: fmt.Sprintf(config, repoName, updated), | ||
| ConfigStateChecks: []statecheck.StateCheck{ | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("policy"), | ||
| knownvalue.StringExact(updated), | ||
| ), | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
| }) | ||
|
|
||
| t.Run("imports without error", func(t *testing.T) { | ||
| randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) | ||
| repoName := fmt.Sprintf("%srepo-pr-policy-%s", testResourcePrefix, randomID) | ||
|
|
||
| config := fmt.Sprintf(` | ||
| resource "github_repository" "test" { | ||
| name = "%s" | ||
| visibility = "private" | ||
| auto_init = true | ||
| } | ||
|
|
||
| resource "github_repository_pull_request_creation_policy" "test" { | ||
| repository = github_repository.test.name | ||
| policy = "collaborators_only" | ||
| } | ||
| `, repoName) | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { skipUnauthenticated(t) }, | ||
| ProviderFactories: providerFactories, | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| Config: config, | ||
| ConfigStateChecks: []statecheck.StateCheck{ | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("repository"), | ||
| knownvalue.StringExact(repoName), | ||
| ), | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("policy"), | ||
| knownvalue.StringExact("collaborators_only"), | ||
| ), | ||
| }, | ||
| }, | ||
| { | ||
| ResourceName: "github_repository_pull_request_creation_policy.test", | ||
| ImportState: true, | ||
| ImportStateVerify: true, | ||
| }, | ||
| }, | ||
| }) | ||
| }) | ||
|
|
||
| t.Run("survives repository rename without replacement", func(t *testing.T) { | ||
| randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) | ||
| repoName := fmt.Sprintf("%srepo-pr-policy-%s", testResourcePrefix, randomID) | ||
| renamedRepoName := fmt.Sprintf("%srepo-pr-policy-renamed-%s", testResourcePrefix, randomID) | ||
|
|
||
| config := ` | ||
| resource "github_repository" "test" { | ||
| name = "%s" | ||
| visibility = "private" | ||
| auto_init = true | ||
| } | ||
|
|
||
| resource "github_repository_pull_request_creation_policy" "test" { | ||
| repository = github_repository.test.name | ||
| policy = "collaborators_only" | ||
| } | ||
| ` | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { skipUnauthenticated(t) }, | ||
| ProviderFactories: providerFactories, | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| Config: fmt.Sprintf(config, repoName), | ||
| ConfigStateChecks: []statecheck.StateCheck{ | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("repository"), | ||
| knownvalue.StringExact(repoName), | ||
| ), | ||
| }, | ||
| }, | ||
| { | ||
| Config: fmt.Sprintf(config, renamedRepoName), | ||
| ConfigPlanChecks: resource.ConfigPlanChecks{ | ||
| PreApply: []plancheck.PlanCheck{ | ||
| plancheck.ExpectResourceAction( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| plancheck.ResourceActionUpdate, | ||
| ), | ||
| }, | ||
| }, | ||
| ConfigStateChecks: []statecheck.StateCheck{ | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("repository"), | ||
| knownvalue.StringExact(renamedRepoName), | ||
| ), | ||
| statecheck.ExpectKnownValue( | ||
| "github_repository_pull_request_creation_policy.test", | ||
| tfjsonpath.New("policy"), | ||
| knownvalue.StringExact("collaborators_only"), | ||
| ), | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add also repository_id and the diffRepo CustomizeDiff configuration to support renaming repositories
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 97d8104: removed
ForceNewfromrepository, added a computedrepository_id, and setCustomizeDiff: diffRepository, per the "Repository as a Required Argument" convention. An acceptance subtest renames the repository and asserts the resource is updated in place rather than replaced, with the policy preserved.