From 245c95aaedf82df3524dd005822d9c22becfa2f0 Mon Sep 17 00:00:00 2001 From: mrymam Date: Wed, 15 Apr 2026 22:43:09 +0900 Subject: [PATCH 1/2] feat: Add github_actions_organization_self_hosted_runners resource Add a new resource to manage self-hosted runner creation policies at the organization level, controlling which repositories are allowed to create repository-level self-hosted runners. Resolves integrations/terraform-provider-github#2601 Co-Authored-By: Claude Opus 4.6 (1M context) --- github/provider.go | 1 + ...ctions_organization_self_hosted_runners.go | 190 ++++++++++++++++++ ...s_organization_self_hosted_runners_test.go | 136 +++++++++++++ ...nization_self_hosted_runners.html.markdown | 54 +++++ 4 files changed, 381 insertions(+) create mode 100644 github/resource_github_actions_organization_self_hosted_runners.go create mode 100644 github/resource_github_actions_organization_self_hosted_runners_test.go create mode 100644 website/docs/r/actions_organization_self_hosted_runners.html.markdown diff --git a/github/provider.go b/github/provider.go index a2829596c7..a871814125 100644 --- a/github/provider.go +++ b/github/provider.go @@ -138,6 +138,7 @@ func Provider() *schema.Provider { "github_actions_environment_variable": resourceGithubActionsEnvironmentVariable(), "github_actions_organization_oidc_subject_claim_customization_template": resourceGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplate(), "github_actions_organization_permissions": resourceGithubActionsOrganizationPermissions(), + "github_actions_organization_self_hosted_runners": resourceGithubActionsOrganizationSelfHostedRunners(), "github_actions_organization_secret": resourceGithubActionsOrganizationSecret(), "github_actions_organization_secret_repositories": resourceGithubActionsOrganizationSecretRepositories(), "github_actions_organization_secret_repository": resourceGithubActionsOrganizationSecretRepository(), diff --git a/github/resource_github_actions_organization_self_hosted_runners.go b/github/resource_github_actions_organization_self_hosted_runners.go new file mode 100644 index 0000000000..7a07f631b1 --- /dev/null +++ b/github/resource_github_actions_organization_self_hosted_runners.go @@ -0,0 +1,190 @@ +package github + +import ( + "context" + "errors" + + "github.com/google/go-github/v84/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceGithubActionsOrganizationSelfHostedRunners() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubActionsOrganizationSelfHostedRunnersCreateOrUpdate, + Read: resourceGithubActionsOrganizationSelfHostedRunnersRead, + Update: resourceGithubActionsOrganizationSelfHostedRunnersCreateOrUpdate, + Delete: resourceGithubActionsOrganizationSelfHostedRunnersDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "enabled_repositories": { + Type: schema.TypeString, + Required: true, + Description: "The policy that controls which repositories in the organization can create self-hosted runners. Can be one of: 'all', 'selected', or 'none'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"all", "selected", "none"}, false)), + }, + "enabled_repositories_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Sets the list of selected repositories that are allowed to create self-hosted runners. Only available when 'enabled_repositories' = 'selected'.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repository_ids": { + Type: schema.TypeSet, + Required: true, + Description: "List of repository IDs allowed to create self-hosted runners.", + Elem: &schema.Schema{Type: schema.TypeInt}, + }, + }, + }, + }, + }, + } +} + +func resourceGithubActionsOrganizationSelfHostedRunnersEnabledRepos(d *schema.ResourceData) ([]int64, error) { + var repoIDs []int64 + + config := d.Get("enabled_repositories_config").([]any) + if len(config) > 0 { + data := config[0].(map[string]any) + switch x := data["repository_ids"].(type) { + case *schema.Set: + for _, value := range x.List() { + repoIDs = append(repoIDs, int64(value.(int))) + } + } + } else { + return nil, errors.New("the enabled_repositories_config {} block must be specified if enabled_repositories == 'selected'") + } + return repoIDs, nil +} + +func resourceGithubActionsOrganizationSelfHostedRunnersCreateOrUpdate(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 { + return err + } + + enabledRepositories := d.Get("enabled_repositories").(string) + + _, err = client.Actions.UpdateSelfHostedRunnersSettingsInOrganization(ctx, + orgName, + github.SelfHostedRunnersSettingsOrganizationOpt{ + EnabledRepositories: &enabledRepositories, + }) + if err != nil { + return err + } + + if enabledRepositories == "selected" { + repoIDs, err := resourceGithubActionsOrganizationSelfHostedRunnersEnabledRepos(d) + if err != nil { + return err + } + _, err = client.Actions.SetRepositoriesSelfHostedRunnersAllowedInOrganization(ctx, + orgName, + repoIDs) + if err != nil { + return err + } + } + + d.SetId(orgName) + return resourceGithubActionsOrganizationSelfHostedRunnersRead(d, meta) +} + +func resourceGithubActionsOrganizationSelfHostedRunnersRead(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.Background() + + err := checkOrganization(meta) + if err != nil { + return err + } + + settings, _, err := client.Actions.GetSelfHostedRunnersSettingsInOrganization(ctx, d.Id()) + if err != nil { + return err + } + + if err = d.Set("enabled_repositories", settings.GetEnabledRepositories()); err != nil { + return err + } + + if settings.GetEnabledRepositories() == "selected" { + opts := github.ListOptions{PerPage: 10, Page: 1} + var repoIDs []int64 + var allRepos []*github.Repository + + for { + result, resp, err := client.Actions.ListRepositoriesSelfHostedRunnersAllowedInOrganization(ctx, d.Id(), &opts) + if err != nil { + return err + } + allRepos = append(allRepos, result.Repositories...) + + opts.Page = resp.NextPage + + if resp.NextPage == 0 { + break + } + } + for index := range allRepos { + repoIDs = append(repoIDs, *allRepos[index].ID) + } + if allRepos != nil { + if err = d.Set("enabled_repositories_config", []any{ + map[string]any{ + "repository_ids": repoIDs, + }, + }); err != nil { + return err + } + } else { + if err = d.Set("enabled_repositories_config", []any{}); err != nil { + return err + } + } + } else { + if err = d.Set("enabled_repositories_config", []any{}); err != nil { + return err + } + } + + return nil +} + +func resourceGithubActionsOrganizationSelfHostedRunnersDelete(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 + } + + allPolicy := "all" + _, err = client.Actions.UpdateSelfHostedRunnersSettingsInOrganization(ctx, + orgName, + github.SelfHostedRunnersSettingsOrganizationOpt{ + EnabledRepositories: &allPolicy, + }) + if err != nil { + return err + } + + return nil +} diff --git a/github/resource_github_actions_organization_self_hosted_runners_test.go b/github/resource_github_actions_organization_self_hosted_runners_test.go new file mode 100644 index 0000000000..391f850161 --- /dev/null +++ b/github/resource_github_actions_organization_self_hosted_runners_test.go @@ -0,0 +1,136 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccGithubActionsOrganizationSelfHostedRunners(t *testing.T) { + t.Run("test setting of basic self-hosted runners policy", func(t *testing.T) { + enabledRepositories := "all" + + config := fmt.Sprintf(` + resource "github_actions_organization_self_hosted_runners" "test" { + enabled_repositories = "%s" + } + `, enabledRepositories) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_organization_self_hosted_runners.test", "enabled_repositories", enabledRepositories, + ), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + }) + + t.Run("test setting selected repositories with import", func(t *testing.T) { + enabledRepositories := "selected" + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-selfhosted-runners-%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%[1]s" + description = "Terraform acceptance tests %[1]s" + topics = ["terraform", "testing"] + } + + resource "github_actions_organization_self_hosted_runners" "test" { + enabled_repositories = "%s" + enabled_repositories_config { + repository_ids = [github_repository.test.repo_id] + } + } + `, repoName, enabledRepositories) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_organization_self_hosted_runners.test", "enabled_repositories", enabledRepositories, + ), + resource.TestCheckResourceAttr( + "github_actions_organization_self_hosted_runners.test", "enabled_repositories_config.#", "1", + ), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + { + ResourceName: "github_actions_organization_self_hosted_runners.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + + t.Run("test updating from all to selected repositories", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-selfhosted-runners-%s", testResourcePrefix, randomID) + + configAll := ` + resource "github_actions_organization_self_hosted_runners" "test" { + enabled_repositories = "all" + } + ` + + configSelected := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%[1]s" + description = "Terraform acceptance tests %[1]s" + topics = ["terraform", "testing"] + } + + resource "github_actions_organization_self_hosted_runners" "test" { + enabled_repositories = "selected" + enabled_repositories_config { + repository_ids = [github_repository.test.repo_id] + } + } + `, repoName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: configAll, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_organization_self_hosted_runners.test", "enabled_repositories", "all", + ), + ), + }, + { + Config: configSelected, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_actions_organization_self_hosted_runners.test", "enabled_repositories", "selected", + ), + resource.TestCheckResourceAttr( + "github_actions_organization_self_hosted_runners.test", "enabled_repositories_config.#", "1", + ), + ), + }, + }, + }) + }) +} diff --git a/website/docs/r/actions_organization_self_hosted_runners.html.markdown b/website/docs/r/actions_organization_self_hosted_runners.html.markdown new file mode 100644 index 0000000000..108fd82852 --- /dev/null +++ b/website/docs/r/actions_organization_self_hosted_runners.html.markdown @@ -0,0 +1,54 @@ +--- +layout: "github" +page_title: "GitHub: github_actions_organization_self_hosted_runners" +description: |- + Creates and manages self-hosted runners settings within a GitHub organization +--- + +# github_actions_organization_self_hosted_runners + +This resource allows you to manage self-hosted runners settings within your GitHub organization. +It controls which repositories are allowed to create repository-level self-hosted runners. +You must have admin access to an organization to use this resource. + +## Example Usage + +```hcl +resource "github_actions_organization_self_hosted_runners" "example" { + enabled_repositories = "all" +} +``` + +```hcl +resource "github_repository" "example" { + name = "my-repository" +} + +resource "github_actions_organization_self_hosted_runners" "example" { + enabled_repositories = "selected" + enabled_repositories_config { + repository_ids = [github_repository.example.repo_id] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `enabled_repositories` - (Required) The policy that controls which repositories in the organization can create self-hosted runners. Can be one of: `all`, `selected`, or `none`. +* `enabled_repositories_config` - (Optional) Sets the list of selected repositories that are allowed to create self-hosted runners. Only available when `enabled_repositories` = `selected`. See [Enabled Repositories Config](#enabled-repositories-config) below for details. + +### Enabled Repositories Config + +The `enabled_repositories_config` block supports the following: + +* `repository_ids` - (Required) List of repository IDs allowed to create self-hosted runners. + +## Import + +This resource can be imported using the name of the GitHub organization: + +``` +$ terraform import github_actions_organization_self_hosted_runners.test github_organization_name +``` From 2a4e8d0d75bc459c9b71f27b0efe3494f2f701aa Mon Sep 17 00:00:00 2001 From: mrymam Date: Wed, 15 Apr 2026 22:50:18 +0900 Subject: [PATCH 2/2] fix lint --- github/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/provider.go b/github/provider.go index a871814125..a4e6ac8407 100644 --- a/github/provider.go +++ b/github/provider.go @@ -138,7 +138,7 @@ func Provider() *schema.Provider { "github_actions_environment_variable": resourceGithubActionsEnvironmentVariable(), "github_actions_organization_oidc_subject_claim_customization_template": resourceGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplate(), "github_actions_organization_permissions": resourceGithubActionsOrganizationPermissions(), - "github_actions_organization_self_hosted_runners": resourceGithubActionsOrganizationSelfHostedRunners(), + "github_actions_organization_self_hosted_runners": resourceGithubActionsOrganizationSelfHostedRunners(), "github_actions_organization_secret": resourceGithubActionsOrganizationSecret(), "github_actions_organization_secret_repositories": resourceGithubActionsOrganizationSecretRepositories(), "github_actions_organization_secret_repository": resourceGithubActionsOrganizationSecretRepository(),