Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ func Provider() *schema.Provider {
"github_user_ssh_key": resourceGithubUserSshKey(),
"github_enterprise_organization": resourceGithubEnterpriseOrganization(),
"github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(),
"github_enterprise_network_configuration": resourceGithubEnterpriseNetworkConfiguration(),
"github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(),
"github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(),
"github_enterprise_security_analysis_settings": resourceGithubEnterpriseSecurityAnalysisSettings(),
Expand Down
96 changes: 92 additions & 4 deletions github/resource_github_enterprise_actions_runner_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import (
"context"
"errors"
"fmt"
"log"
"net/http"
"strconv"
"strings"

"github.com/google/go-github/v83/github"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

type enterpriseRunnerGroup struct {
NetworkConfigurationID *string `json:"network_configuration_id,omitempty"`
}

func resourceGithubActionsEnterpriseRunnerGroup() *schema.Resource {
return &schema.Resource{
Create: resourceGithubActionsEnterpriseRunnerGroupCreate,
Expand Down Expand Up @@ -74,6 +78,12 @@ func resourceGithubActionsEnterpriseRunnerGroup() *schema.Resource {
Optional: true,
Description: "List of workflows the runner group should be allowed to run. This setting will be ignored unless restricted_to_workflows is set to 'true'.",
},
"network_configuration_id": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(1, 255),
Description: "The identifier of the hosted compute network configuration to associate with this runner group for GitHub-hosted private networking.",
},
"selected_organization_ids": {
Type: schema.TypeSet,
Elem: &schema.Schema{
Expand All @@ -92,6 +102,53 @@ func resourceGithubActionsEnterpriseRunnerGroup() *schema.Resource {
}
}

func getEnterpriseRunnerGroupNetworking(client *github.Client, ctx context.Context, enterprise string, groupID int64) (*enterpriseRunnerGroup, *github.Response, error) {
req, err := client.NewRequest("GET", fmt.Sprintf("enterprises/%s/actions/runner-groups/%d", enterprise, groupID), nil)
if err != nil {
return nil, nil, err
}

var runnerGroup enterpriseRunnerGroup
resp, err := client.Do(ctx, req, &runnerGroup)
if err != nil {
return nil, resp, err
}

return &runnerGroup, resp, nil
}

func updateEnterpriseRunnerGroupNetworking(client *github.Client, ctx context.Context, enterprise string, groupID int64, networkConfigurationID *string) (*github.Response, error) {
payload := map[string]any{
"network_configuration_id": networkConfigurationID,
}

req, err := client.NewRequest("PATCH", fmt.Sprintf("enterprises/%s/actions/runner-groups/%d", enterprise, groupID), payload)
if err != nil {
return nil, err
}

resp, err := client.Do(ctx, req, nil)
if err != nil {
return resp, err
}

return resp, nil
}

func setGithubActionsEnterpriseRunnerGroupNetworkingState(d *schema.ResourceData, runnerGroup *enterpriseRunnerGroup) error {
if runnerGroup != nil && runnerGroup.NetworkConfigurationID != nil && *runnerGroup.NetworkConfigurationID != "" {
if err := d.Set("network_configuration_id", *runnerGroup.NetworkConfigurationID); err != nil {
return err
}
} else {
if err := d.Set("network_configuration_id", nil); err != nil {
return err
}
}

return nil
}

func resourceGithubActionsEnterpriseRunnerGroupCreate(d *schema.ResourceData, meta any) error {
client := meta.(*Owner).v3client

Expand Down Expand Up @@ -174,6 +231,13 @@ func resourceGithubActionsEnterpriseRunnerGroupCreate(d *schema.ResourceData, me
return err
}

if networkConfigurationID, ok := d.GetOk("network_configuration_id"); ok {
networkConfigurationIDValue := networkConfigurationID.(string)
if _, err = updateEnterpriseRunnerGroupNetworking(client, ctx, enterpriseSlug, enterpriseRunnerGroup.GetID(), &networkConfigurationIDValue); err != nil {
return err
}
}

return resourceGithubActionsEnterpriseRunnerGroupRead(d, meta)
}

Expand All @@ -198,6 +262,8 @@ func resourceGithubActionsEnterpriseRunnerGroupRead(d *schema.ResourceData, meta
return err
}
ctx := context.WithValue(context.Background(), ctxId, d.Id())
ctx = tflog.SetField(ctx, "enterprise_slug", enterpriseSlug)
ctx = tflog.SetField(ctx, "id", d.Id())
if !d.IsNewResource() {
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
}
Expand All @@ -207,8 +273,7 @@ func resourceGithubActionsEnterpriseRunnerGroupRead(d *schema.ResourceData, meta
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) {
if ghErr.Response.StatusCode == http.StatusNotFound {
log.Printf("[INFO] Removing enterprise runner group %s/%s from state because it no longer exists in GitHub",
enterpriseSlug, d.Id())
tflog.Info(ctx, "Removing enterprise runner group from state because it no longer exists in GitHub")
d.SetId("")
return nil
}
Expand Down Expand Up @@ -252,6 +317,14 @@ func resourceGithubActionsEnterpriseRunnerGroupRead(d *schema.ResourceData, meta
return err
}

runnerGroupNetworking, _, err := getEnterpriseRunnerGroupNetworking(client, context.WithValue(context.Background(), ctxId, d.Id()), enterpriseSlug, runnerGroupID)
if err != nil {
return err
}
if err = setGithubActionsEnterpriseRunnerGroupNetworkingState(d, runnerGroupNetworking); err != nil {
return err
}

selectedOrganizationIDs := []int64{}
optionsOrgs := github.ListOptions{
PerPage: maxPerPage,
Expand Down Expand Up @@ -314,6 +387,18 @@ func resourceGithubActionsEnterpriseRunnerGroupUpdate(d *schema.ResourceData, me
return err
}

if d.HasChange("network_configuration_id") {
var networkConfigurationIDValue *string
if networkConfigurationID, ok := d.GetOk("network_configuration_id"); ok {
value := networkConfigurationID.(string)
networkConfigurationIDValue = &value
}

if _, err := updateEnterpriseRunnerGroupNetworking(client, ctx, enterpriseSlug, runnerGroupID, networkConfigurationIDValue); err != nil {
return err
}
}

selectedOrganizations, hasSelectedOrganizations := d.GetOk("selected_organization_ids")
selectedOrganizationIDs := []int64{}

Expand Down Expand Up @@ -342,8 +427,11 @@ func resourceGithubActionsEnterpriseRunnerGroupDelete(d *schema.ResourceData, me
return err
}
ctx := context.WithValue(context.Background(), ctxId, d.Id())
ctx = tflog.SetField(ctx, "enterprise_slug", enterpriseSlug)
ctx = tflog.SetField(ctx, "id", d.Id())
ctx = tflog.SetField(ctx, "name", d.Get("name"))

log.Printf("[INFO] Deleting enterprise runner group: %s/%s (%s)", enterpriseSlug, d.Get("name"), d.Id())
tflog.Debug(ctx, "Deleting enterprise runner group")
_, err = client.Enterprise.DeleteEnterpriseRunnerGroup(ctx, enterpriseSlug, enterpriseRunnerGroupID)
return err
}
Expand Down
91 changes: 91 additions & 0 deletions github/resource_github_enterprise_actions_runner_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package github

import (
"fmt"
"os"
"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/statecheck"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
)

func TestAccGithubActionsEnterpriseRunnerGroup(t *testing.T) {
Expand Down Expand Up @@ -193,4 +197,91 @@ func TestAccGithubActionsEnterpriseRunnerGroup(t *testing.T) {
},
})
})

t.Run("manages runner group network configuration", func(t *testing.T) {
networkSettingsID := testAccEnterpriseNetworkConfigurationID(t)
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
resourceName := "github_enterprise_actions_runner_group.test"
networkConfigurationResourceName := "github_enterprise_network_configuration.test"
groupName := fmt.Sprintf("tf-acc-test-%s", randomID)
networkConfigurationName := fmt.Sprintf("%senterprise-network-config-%s", testResourcePrefix, randomID)

configWithoutNetworking := fmt.Sprintf(`
resource "github_enterprise_network_configuration" "test" {
enterprise_slug = %q
name = %q
compute_service = "actions"
network_settings_ids = [%q]
}

resource "github_enterprise_actions_runner_group" "test" {
enterprise_slug = %q
name = %q
visibility = "all"
}
`, testAccConf.enterpriseSlug, networkConfigurationName, networkSettingsID, testAccConf.enterpriseSlug, groupName)

configWithNetworking := fmt.Sprintf(`
resource "github_enterprise_network_configuration" "test" {
enterprise_slug = %q
name = %q
compute_service = "actions"
network_settings_ids = [%q]
}

resource "github_enterprise_actions_runner_group" "test" {
enterprise_slug = %q
name = %q
visibility = "all"
network_configuration_id = github_enterprise_network_configuration.test.id
}
`, testAccConf.enterpriseSlug, networkConfigurationName, networkSettingsID, testAccConf.enterpriseSlug, groupName)

resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, enterprise) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: configWithoutNetworking,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("name"), knownvalue.StringExact(groupName)),
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("visibility"), knownvalue.StringExact("all")),
},
Check: resource.ComposeTestCheckFunc(
resource.TestCheckNoResourceAttr(resourceName, "network_configuration_id"),
),
},
{
Config: configWithNetworking,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "network_configuration_id"),
resource.TestCheckResourceAttrPair(resourceName, "network_configuration_id", networkConfigurationResourceName, "id"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateIdPrefix: fmt.Sprintf(`%s/`, testAccConf.enterpriseSlug),
},
{
Config: configWithoutNetworking,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckNoResourceAttr(resourceName, "network_configuration_id"),
),
},
},
})
})
}

func testAccEnterpriseNetworkConfigurationID(t *testing.T) string {
t.Helper()

networkSettingsID := os.Getenv("GITHUB_TEST_ENTERPRISE_NETWORK_SETTINGS_ID")
if networkSettingsID == "" {
t.Skip("GITHUB_TEST_ENTERPRISE_NETWORK_SETTINGS_ID not set")
}

return networkSettingsID
}
Loading