Skip to content

Commit c195eac

Browse files
committed
feat: add support for GitHub enterprise network configurations and update related documentation
1 parent 6127464 commit c195eac

8 files changed

Lines changed: 626 additions & 4 deletions

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ func Provider() *schema.Provider {
215215
"github_user_ssh_key": resourceGithubUserSshKey(),
216216
"github_enterprise_organization": resourceGithubEnterpriseOrganization(),
217217
"github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(),
218+
"github_enterprise_network_configuration": resourceGithubEnterpriseNetworkConfiguration(),
218219
"github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(),
219220
"github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(),
220221
"github_enterprise_security_analysis_settings": resourceGithubEnterpriseSecurityAnalysisSettings(),

github/resource_github_enterprise_actions_runner_group.go

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"log"
87
"net/http"
98
"strconv"
109
"strings"
1110

1211
"github.com/google/go-github/v83/github"
12+
"github.com/hashicorp/terraform-plugin-log/tflog"
1313
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1414
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1515
)
1616

17+
type enterpriseRunnerGroup struct {
18+
NetworkConfigurationID *string `json:"network_configuration_id,omitempty"`
19+
}
20+
1721
func resourceGithubActionsEnterpriseRunnerGroup() *schema.Resource {
1822
return &schema.Resource{
1923
Create: resourceGithubActionsEnterpriseRunnerGroupCreate,
@@ -74,6 +78,12 @@ func resourceGithubActionsEnterpriseRunnerGroup() *schema.Resource {
7478
Optional: true,
7579
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'.",
7680
},
81+
"network_configuration_id": {
82+
Type: schema.TypeString,
83+
Optional: true,
84+
ValidateFunc: validation.StringLenBetween(1, 255),
85+
Description: "The identifier of the hosted compute network configuration to associate with this runner group for GitHub-hosted private networking.",
86+
},
7787
"selected_organization_ids": {
7888
Type: schema.TypeSet,
7989
Elem: &schema.Schema{
@@ -92,6 +102,53 @@ func resourceGithubActionsEnterpriseRunnerGroup() *schema.Resource {
92102
}
93103
}
94104

105+
func getEnterpriseRunnerGroupNetworking(client *github.Client, ctx context.Context, enterprise string, groupID int64) (*enterpriseRunnerGroup, *github.Response, error) {
106+
req, err := client.NewRequest("GET", fmt.Sprintf("enterprises/%s/actions/runner-groups/%d", enterprise, groupID), nil)
107+
if err != nil {
108+
return nil, nil, err
109+
}
110+
111+
var runnerGroup enterpriseRunnerGroup
112+
resp, err := client.Do(ctx, req, &runnerGroup)
113+
if err != nil {
114+
return nil, resp, err
115+
}
116+
117+
return &runnerGroup, resp, nil
118+
}
119+
120+
func updateEnterpriseRunnerGroupNetworking(client *github.Client, ctx context.Context, enterprise string, groupID int64, networkConfigurationID *string) (*github.Response, error) {
121+
payload := map[string]any{
122+
"network_configuration_id": networkConfigurationID,
123+
}
124+
125+
req, err := client.NewRequest("PATCH", fmt.Sprintf("enterprises/%s/actions/runner-groups/%d", enterprise, groupID), payload)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
resp, err := client.Do(ctx, req, nil)
131+
if err != nil {
132+
return resp, err
133+
}
134+
135+
return resp, nil
136+
}
137+
138+
func setGithubActionsEnterpriseRunnerGroupNetworkingState(d *schema.ResourceData, runnerGroup *enterpriseRunnerGroup) error {
139+
if runnerGroup != nil && runnerGroup.NetworkConfigurationID != nil && *runnerGroup.NetworkConfigurationID != "" {
140+
if err := d.Set("network_configuration_id", *runnerGroup.NetworkConfigurationID); err != nil {
141+
return err
142+
}
143+
} else {
144+
if err := d.Set("network_configuration_id", nil); err != nil {
145+
return err
146+
}
147+
}
148+
149+
return nil
150+
}
151+
95152
func resourceGithubActionsEnterpriseRunnerGroupCreate(d *schema.ResourceData, meta any) error {
96153
client := meta.(*Owner).v3client
97154

@@ -174,6 +231,13 @@ func resourceGithubActionsEnterpriseRunnerGroupCreate(d *schema.ResourceData, me
174231
return err
175232
}
176233

234+
if networkConfigurationID, ok := d.GetOk("network_configuration_id"); ok {
235+
networkConfigurationIDValue := networkConfigurationID.(string)
236+
if _, err = updateEnterpriseRunnerGroupNetworking(client, ctx, enterpriseSlug, enterpriseRunnerGroup.GetID(), &networkConfigurationIDValue); err != nil {
237+
return err
238+
}
239+
}
240+
177241
return resourceGithubActionsEnterpriseRunnerGroupRead(d, meta)
178242
}
179243

@@ -198,6 +262,8 @@ func resourceGithubActionsEnterpriseRunnerGroupRead(d *schema.ResourceData, meta
198262
return err
199263
}
200264
ctx := context.WithValue(context.Background(), ctxId, d.Id())
265+
ctx = tflog.SetField(ctx, "enterprise_slug", enterpriseSlug)
266+
ctx = tflog.SetField(ctx, "id", d.Id())
201267
if !d.IsNewResource() {
202268
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
203269
}
@@ -207,8 +273,7 @@ func resourceGithubActionsEnterpriseRunnerGroupRead(d *schema.ResourceData, meta
207273
var ghErr *github.ErrorResponse
208274
if errors.As(err, &ghErr) {
209275
if ghErr.Response.StatusCode == http.StatusNotFound {
210-
log.Printf("[INFO] Removing enterprise runner group %s/%s from state because it no longer exists in GitHub",
211-
enterpriseSlug, d.Id())
276+
tflog.Info(ctx, "Removing enterprise runner group from state because it no longer exists in GitHub")
212277
d.SetId("")
213278
return nil
214279
}
@@ -252,6 +317,14 @@ func resourceGithubActionsEnterpriseRunnerGroupRead(d *schema.ResourceData, meta
252317
return err
253318
}
254319

320+
runnerGroupNetworking, _, err := getEnterpriseRunnerGroupNetworking(client, context.WithValue(context.Background(), ctxId, d.Id()), enterpriseSlug, runnerGroupID)
321+
if err != nil {
322+
return err
323+
}
324+
if err = setGithubActionsEnterpriseRunnerGroupNetworkingState(d, runnerGroupNetworking); err != nil {
325+
return err
326+
}
327+
255328
selectedOrganizationIDs := []int64{}
256329
optionsOrgs := github.ListOptions{
257330
PerPage: maxPerPage,
@@ -314,6 +387,18 @@ func resourceGithubActionsEnterpriseRunnerGroupUpdate(d *schema.ResourceData, me
314387
return err
315388
}
316389

390+
if d.HasChange("network_configuration_id") {
391+
var networkConfigurationIDValue *string
392+
if networkConfigurationID, ok := d.GetOk("network_configuration_id"); ok {
393+
value := networkConfigurationID.(string)
394+
networkConfigurationIDValue = &value
395+
}
396+
397+
if _, err := updateEnterpriseRunnerGroupNetworking(client, ctx, enterpriseSlug, runnerGroupID, networkConfigurationIDValue); err != nil {
398+
return err
399+
}
400+
}
401+
317402
selectedOrganizations, hasSelectedOrganizations := d.GetOk("selected_organization_ids")
318403
selectedOrganizationIDs := []int64{}
319404

@@ -342,8 +427,11 @@ func resourceGithubActionsEnterpriseRunnerGroupDelete(d *schema.ResourceData, me
342427
return err
343428
}
344429
ctx := context.WithValue(context.Background(), ctxId, d.Id())
430+
ctx = tflog.SetField(ctx, "enterprise_slug", enterpriseSlug)
431+
ctx = tflog.SetField(ctx, "id", d.Id())
432+
ctx = tflog.SetField(ctx, "name", d.Get("name"))
345433

346-
log.Printf("[INFO] Deleting enterprise runner group: %s/%s (%s)", enterpriseSlug, d.Get("name"), d.Id())
434+
tflog.Debug(ctx, "Deleting enterprise runner group")
347435
_, err = client.Enterprise.DeleteEnterpriseRunnerGroup(ctx, enterpriseSlug, enterpriseRunnerGroupID)
348436
return err
349437
}

github/resource_github_enterprise_actions_runner_group_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package github
22

33
import (
44
"fmt"
5+
"os"
56
"testing"
67

78
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
89

910
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
12+
"github.com/hashicorp/terraform-plugin-testing/statecheck"
13+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
1014
)
1115

1216
func TestAccGithubActionsEnterpriseRunnerGroup(t *testing.T) {
@@ -193,4 +197,91 @@ func TestAccGithubActionsEnterpriseRunnerGroup(t *testing.T) {
193197
},
194198
})
195199
})
200+
201+
t.Run("manages runner group network configuration", func(t *testing.T) {
202+
networkSettingsID := testAccEnterpriseNetworkConfigurationID(t)
203+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
204+
resourceName := "github_enterprise_actions_runner_group.test"
205+
networkConfigurationResourceName := "github_enterprise_network_configuration.test"
206+
groupName := fmt.Sprintf("tf-acc-test-%s", randomID)
207+
networkConfigurationName := fmt.Sprintf("%senterprise-network-config-%s", testResourcePrefix, randomID)
208+
209+
configWithoutNetworking := fmt.Sprintf(`
210+
resource "github_enterprise_network_configuration" "test" {
211+
enterprise_slug = %q
212+
name = %q
213+
compute_service = "actions"
214+
network_settings_ids = [%q]
215+
}
216+
217+
resource "github_enterprise_actions_runner_group" "test" {
218+
enterprise_slug = %q
219+
name = %q
220+
visibility = "all"
221+
}
222+
`, testAccConf.enterpriseSlug, networkConfigurationName, networkSettingsID, testAccConf.enterpriseSlug, groupName)
223+
224+
configWithNetworking := fmt.Sprintf(`
225+
resource "github_enterprise_network_configuration" "test" {
226+
enterprise_slug = %q
227+
name = %q
228+
compute_service = "actions"
229+
network_settings_ids = [%q]
230+
}
231+
232+
resource "github_enterprise_actions_runner_group" "test" {
233+
enterprise_slug = %q
234+
name = %q
235+
visibility = "all"
236+
network_configuration_id = github_enterprise_network_configuration.test.id
237+
}
238+
`, testAccConf.enterpriseSlug, networkConfigurationName, networkSettingsID, testAccConf.enterpriseSlug, groupName)
239+
240+
resource.Test(t, resource.TestCase{
241+
PreCheck: func() { skipUnlessMode(t, enterprise) },
242+
ProviderFactories: providerFactories,
243+
Steps: []resource.TestStep{
244+
{
245+
Config: configWithoutNetworking,
246+
ConfigStateChecks: []statecheck.StateCheck{
247+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("name"), knownvalue.StringExact(groupName)),
248+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("visibility"), knownvalue.StringExact("all")),
249+
},
250+
Check: resource.ComposeTestCheckFunc(
251+
resource.TestCheckNoResourceAttr(resourceName, "network_configuration_id"),
252+
),
253+
},
254+
{
255+
Config: configWithNetworking,
256+
Check: resource.ComposeTestCheckFunc(
257+
resource.TestCheckResourceAttrSet(resourceName, "network_configuration_id"),
258+
resource.TestCheckResourceAttrPair(resourceName, "network_configuration_id", networkConfigurationResourceName, "id"),
259+
),
260+
},
261+
{
262+
ResourceName: resourceName,
263+
ImportState: true,
264+
ImportStateVerify: true,
265+
ImportStateIdPrefix: fmt.Sprintf(`%s/`, testAccConf.enterpriseSlug),
266+
},
267+
{
268+
Config: configWithoutNetworking,
269+
Check: resource.ComposeTestCheckFunc(
270+
resource.TestCheckNoResourceAttr(resourceName, "network_configuration_id"),
271+
),
272+
},
273+
},
274+
})
275+
})
276+
}
277+
278+
func testAccEnterpriseNetworkConfigurationID(t *testing.T) string {
279+
t.Helper()
280+
281+
networkSettingsID := os.Getenv("GITHUB_TEST_ENTERPRISE_NETWORK_SETTINGS_ID")
282+
if networkSettingsID == "" {
283+
t.Skip("GITHUB_TEST_ENTERPRISE_NETWORK_SETTINGS_ID not set")
284+
}
285+
286+
return networkSettingsID
196287
}

0 commit comments

Comments
 (0)