Skip to content

feat: add hosted runner private networking support - #3274

Open
austenstone wants to merge 45 commits into
integrations:mainfrom
austenstone:feature/github-enterprise-network-configurations
Open

feat: add hosted runner private networking support#3274
austenstone wants to merge 45 commits into
integrations:mainfrom
austenstone:feature/github-enterprise-network-configurations

Conversation

@austenstone

@austenstone austenstone commented Mar 15, 2026

Copy link
Copy Markdown
Contributor

Resolves #2302


Before the change?

Hosted-runner private networking cannot be managed end to end through the provider: network configurations and their runner-group associations need to be managed separately from Terraform.

After the change?

Manage hosted-compute network configurations and runner-group associations at both organization and enterprise scope. Both runner-group POST endpoints accept network_configuration_id; an extra networking-only PATCH after creation is not required. The previous description was incorrect about that.

Not ready to merge: removing an association still replaces the runner group in this branch. google/go-github#4541 adds the explicit-null serialization needed for in-place removal. It needs to be merged and released, then adopted here before replacing the current removal behavior. The REST API already supports null; this is a client-library limitation.

The configuration resources and runner-group integration remain together here. The earlier request to split that scope still needs maintainer agreement.

Implementation and cleanup were substantially AI-assisted with Copilot.

Pull request checklist

  • Schema migrations have been created if needed (example) — not needed for these new resources and optional attributes.
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)

Targeted Go 1.26 tests, strict changed-code lint, example formatting, and generated-documentation checks pass locally. Fixture requirements are documented in CONTRIBUTING.md.

Live validation on September 10, 2026, against the implementation in this PR:

  • All six selected acceptance subtests ran and passed, with no skips: three organization cases and three enterprise cases. They covered network-configuration create/import/update/destroy, runner-group assignment/import/removal, and networking assigned during group creation. The removal cases still exercise the current replacement behavior, not an in-place fix.
  • Two real GitHub-hosted Linux runners completed a TCP/HTTP GET-and-POST handshake on port 8080 between distinct private addresses in a fresh delegated Azure subnet with explicit NAT egress. Both jobs checked the run ID, expected subnet, and peer addresses.
  • A separate direct API experiment confirmed that explicit null removes the enterprise group's association while preserving both its group ID and hosted-runner configuration. This validates the API contract behind the SDK change; it does not mean the provider's removal path is fixed.

Enterprise hosted-runner provisioning used the supported enterprise API. The existing github_actions_hosted_runner resource is organization-only and rejected an inherited enterprise group ID. Organization access to an enterprise group also required separate repository access within the organization.

An older shared network fixture left jobs waiting for a runner to come online; the fresh isolated fixture succeeded. The older fixture's specific failure cause remains unproven, and no shared Azure network or enterprise policy was changed. These local/live runs are separate from the PR's acceptance CI status.

Does this introduce a breaking change?

Please see our docs on breaking changes to help!

  • Yes
  • No

Existing configurations without the new attributes retain their behavior. The new association-removal path must become non-destructive before this PR merges.


@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for this contribution! Just to let you know, our GitHub SDK team does a round of issue and PR reviews twice a week, every Monday and Friday! We have a process in place for prioritizing and responding to your input. Because you are a part of this community please feel free to comment, add to, or pick up any issues/PRs that are labeled with Status: Up for grabs. You & others like you are the reason all of this works! So thank you & happy coding! 🚀

@github-actions github-actions Bot added the Type: Feature New feature or request label Mar 15, 2026
@deiga

deiga commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator

Your validation commands would not actually run the tests. You should use make testacc and make test

tag-assistant

This comment was marked as resolved.

@austenstone

This comment was marked as resolved.

@tag-assistant tag-assistant left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review ✅

All previous feedback addressed, plus some nice extras:

Addressed:

  • ✅ Shared runnerGroupNetworking struct + helpers in resource_github_actions_runner_group_networking.go — clean dedup
  • getRunnerGroupNetworking now takes a path string, works for both org and enterprise scopes
  • ✅ Trailing newline fixed on enterprise docs
  • StatusNotModified handling added to getRunnerGroupNetworking — consistent with the rest of the codebase

Bonus improvements (not requested):

  • 🧪 173-line unit test file for all three shared helpers (get, update, setState) — including 304 handling and null payload for removal. Solid.
  • 🔧 normalizeEtag() in util.go to handle weak vs strong ETag drift between create and read paths — nice catch, prevents state churn.
  • 🧪 Unit tests for normalizeEtag covering empty, strong, weak, and whitespace cases.

LGTM. Ship it 🚢

@deiga deiga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're modifying this file, please migrate the CRUD functions to be Context-aware

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — this file now uses context-aware CRUD handlers (CreateContext/ReadContext/UpdateContext/DeleteContext).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still undone

Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group_helpers.go Outdated
Comment thread github/resource_github_enterprise_actions_runner_group.go Outdated
Comment thread github/resource_github_enterprise_actions_runner_group.go
Comment thread github/resource_github_enterprise_actions_runner_group.go Outdated
Comment thread github/resource_github_enterprise_actions_runner_group.go Outdated
Comment thread github/resource_github_enterprise_actions_runner_group_test.go Outdated

@deiga deiga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_actions_runner_group_helpers.go Outdated
Comment thread github/resource_github_actions_runner_group_helpers_test.go Outdated
austenstone added a commit to austenstone/go-github that referenced this pull request Mar 16, 2026
…unner group types

Add NetworkConfigurationID and HostedRunnersURL fields to EnterpriseRunnerGroup,
and NetworkConfigurationID to CreateEnterpriseRunnerGroupRequest and
UpdateEnterpriseRunnerGroupRequest to match the GitHub API response schema.

These fields already exist on the organization-scoped RunnerGroup type but were
missing from the enterprise equivalents. The GitHub API returns both fields on
enterprise runner group endpoints (List, Get, Create, Update).

Fixes: integrations/terraform-provider-github#3274
@austenstone

Copy link
Copy Markdown
Contributor Author

All review feedback addressed except the raw NewRequest/Do calls — those are blocked on google/go-github#4099 which adds NetworkConfigurationID to the runner group structs. Once that merges and we bump the dependency, the helpers file goes away and networking flows through the native UpdateOrganizationRunnerGroup/UpdateEnterpriseRunnerGroup functions.

@austenstone
austenstone force-pushed the feature/github-enterprise-network-configurations branch 2 times, most recently from 508630f to 022c485 Compare March 20, 2026 14:18
austenstone and others added 8 commits August 3, 2026 12:01
# Conflicts:
#	github/resource_github_actions_runner_group.go
#	github/resource_github_actions_runner_group_test.go
#	github/resource_github_enterprise_actions_runner_group.go
- Update to go-github v89
- Use errors.AsType per current repo idiom
- Share the state setter and network settings expansion between scopes
- Add ForceNew to enterprise_slug so moving scope replaces the resource
- Handle 304 Not Modified in Read

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Assign a hosted compute network configuration to an organization or
enterprise runner group. The request object is built up front and the
optional field is set inside the conditional.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Remove the unused normalizeEtag helper and its test
- Remove the runner group unit test file that covered pre-existing helpers
- Align network configuration test destroy checks with testAccConf.meta
- Replace a removed network configuration by ForceNew, since the API cannot
  unset the assignment through an update

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Docs in this repo are generated by tfplugindocs from templates/ and
examples/, so hand-edited pages under docs/ get overwritten. Add the
missing templates and example configurations, document
network_configuration_id on both runner groups, and correct the hosted
runner example which used a payload key rather than the schema attribute.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The branch had accumulated go.sum entries not required by go.mod.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…urces

ctxId is written by many resources but never read; only ctxEtag is
consumed by the etag transport. The network configuration resources do
not expose an etag attribute, so threading ctxId through was noise.

Also align the enterprise import error message with the wording used by
the enterprise runner group importer.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The strict linter config used for new code enables forcetypeassert, so
unchecked assertions in new code fail CI. Adopt the pattern already used
by the organization role data sources: take the provider meta as `m any`,
assert it once into a typed `meta`, and use checkOrganizationOK, which
returns diagnostics directly rather than an error.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@austenstone

Copy link
Copy Markdown
Contributor Author

Thanks for the patience on this one, and sorry for the long gap. I've rebuilt the PR against current main and worked through all the review feedback. Summary of what changed:

Scope reduction

The biggest problem with the previous state was that it dragged both runner group resources through an unrelated context/diag migration. Both resource_github_actions_runner_group.go and resource_github_enterprise_actions_runner_group.go are now reverted to main with only the network_configuration_id additions layered on top. That drops the helper extraction, the ctx = context.WithValue(ctx, ctxId, …) churn, the errors.Aserrors.AsType rewrite, and the ghErr.Response != nil guard changes — all of which you flagged and none of which this feature needs.

The _unit_test.go file is gone too. normalizeEtag went with it, since nothing called it after the revert.

Review threads

Thread Resolution
getOrganizationRunnerGroup / setGithubActionsRunnerGroupState extraction Reverted, untouched
networkConfigurationIDPtr dance Request object is now built up front and the optional field is set inside the if, as you suggested
ctx = context.WithValue(ctx, ctxId, …) Reverted. Also dropped from the new resources — ctxId is written in ~40 places and read in exactly zero, only ctxEtag is consumed by the transport
errors.As / ghErr.Response != nil Reverted in the runner groups; the new resources use errors.AsType with no nil guard, matching the current idiom
_unit_test.go naming File deleted

New resources

github_organization_network_configuration and github_enterprise_network_configuration are rewritten against go-github v89's native Organizations.*NetworkConfiguration* and Enterprise.*EnterpriseNetworkConfiguration* methods. No raw REST anywhere, which resolves @deiga's blocker.

They follow the pattern the organization role data sources use: m any asserted once into a typed meta, checkOrganizationOK for the org-scope guard, CreateContext/ReadContext/… returning diag.Diagnostics. enterprise_slug is ForceNew. Every attribute has a Description and bounded inputs have ValidateDiagFunc (name length/charset mirroring the API constraint, compute_service restricted to none/actions since go-github's own client-side validation rejects codespaces).

422s from a cross-scope network settings ID get annotated, since mixing up an org-scoped and an enterprise-scoped GitHub.Network/networkSettings GitHub ID is the easiest mistake to make here and the bare API error doesn't say so.

network_configuration_id removal forces replacement

Worth calling out explicitly. PATCH /orgs/{org}/actions/runner-groups/{id} declares network_configuration_id as type: string, nullable: true, so an explicit null unsets it. But go-github's UpdateRunnerGroupRequest.NetworkConfigurationID is *string with json:",omitempty", so a nil pointer omits the key entirely and there's no way to send explicit null. Rather than leave a perpetual diff, removing the attribute now forces replacement via customdiff.ForceNewIfChange. Changing to a different non-empty ID is still an in-place update. Happy to swap this for an upstream go-github fix if you'd rather wait for one.

Docs

I hadn't realised docs/ is generated — the earlier revision hand-edited those pages, which make checkdocs would have blown away. There are now templates/resources/*.md.tmpl and examples/resources/*/example_1.tf for both new resources, plus network_configuration_id documented on both runner groups and a note on the hosted runner page clarifying that private networking is a runner-group concern (public_ip_enabled is unrelated). The examples/hosted_runner config also used enable_static_ip, which is the API payload key, not the schema attribute — fixed to public_ip_enabled.

Verification

make lintcheck, make lintcheck-new, make test, make checkdocs, make validatedocs, and rumdl check ./docs all pass locally. go.sum had picked up entries go mod tidy doesn't want; that's cleaned up too. CI runs are sitting in action_required — a maintainer approval would be appreciated.

Acceptance tests are gated behind GITHUB_TEST_NETWORK_SETTINGS_ID / GITHUB_TEST_ENTERPRISE_NETWORK_SETTINGS_ID since they need a real Azure vnet registered against the org/enterprise.

@stevehipwell @deiga — would appreciate another look when you have a moment.

@deiga deiga removed Status: Blocked Some technical or requirement is blocking the issue Upstream labels Aug 4, 2026

@deiga deiga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

Comment on lines +406 to +409
// networkConfigurationRemoved reports whether a hosted compute network configuration was
// removed from a runner group. The REST API only accepts an explicit null to clear the
// assignment, which the client library's request type cannot express, so the runner group is
// replaced instead of leaving the practitioner with a perpetual diff.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Please elaborate on this.
The API docs don't contain any mention of this and it sounds like an go-github ~bug that the field is omitempty. Could you open a PR there to modify the struct?

Comment on lines +182 to +184
if err = d.Set("network_configuration_id", runnerGroup.GetNetworkConfigurationID()); err != nil {
return err
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Since we're setting the value here, should the field be marked as Computed: true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept this Optional without Computed intentionally. Reading the configured value back is also needed for refresh/import; it doesn't make the field server-controlled. Optional+Computed would retain the previous assignment when the attribute is omitted, rather than treating omission as a request to remove it. The separate SDK null-serialization thread is still open.

@deiga deiga Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/data-consistency-errors#finding-data-consistency-errors

Resources should never set or change an attribute value without the schema Computed flag.

Comment thread github/resource_github_actions_runner_group.go Outdated
Comment thread github/resource_github_enterprise_actions_runner_group_test.go Outdated
Comment thread github/resource_github_enterprise_actions_runner_group_test.go Outdated
@deiga

deiga commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Please also note that some of the resource you modify here are being changed in this PR. It's recommended to use that as a base

@deiga
deiga requested a balanced review from Copilot and removed request for tag-assistant August 4, 2026 08:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

These provider review instructions are being used.

Adds enterprise and organization hosted-compute network configuration resources and runner-group associations.

Changes:

  • Adds network configuration CRUD, imports, registration, tests, examples, and docs.
  • Adds runner-group network_configuration_id support.
  • Updates hosted-runner networking guidance.

Unresolved issues include create-endpoint contract mismatches, missing organization acceptance coverage, and the unimplemented 304-only error handling described by the PR.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
templates/resources/organization_network_configuration.md.tmpl Adds organization resource template.
templates/resources/enterprise_network_configuration.md.tmpl Adds enterprise resource template.
templates/resources/enterprise_actions_runner_group.md.tmpl Documents enterprise networking association.
templates/resources/actions_runner_group.md.tmpl Documents organization networking association.
templates/resources/actions_hosted_runner.md.tmpl Clarifies private networking configuration.
github/resource_github_organization_network_configuration.go Implements organization CRUD and import.
github/resource_github_organization_network_configuration_test.go Adds organization resource acceptance tests.
github/resource_github_enterprise_network_configuration.go Implements enterprise CRUD and import.
github/resource_github_enterprise_network_configuration_test.go Adds enterprise resource acceptance tests.
github/resource_github_enterprise_actions_runner_group.go Adds enterprise runner-group association.
github/resource_github_enterprise_actions_runner_group_test.go Tests enterprise association flows.
github/resource_github_actions_runner_group.go Adds organization runner-group association.
github/provider.go Registers both resources.
examples/resources/organization_network_configuration/example_1.tf Demonstrates organization networking.
examples/resources/enterprise_network_configuration/example_1.tf Demonstrates enterprise networking.
examples/hosted_runner/main.tf Updates public-IP argument.
docs/resources/organization_network_configuration.md Adds generated organization documentation.
docs/resources/enterprise_network_configuration.md Adds generated enterprise documentation.
docs/resources/enterprise_actions_runner_group.md Documents enterprise association.
docs/resources/actions_runner_group.md Documents organization association.
docs/resources/actions_hosted_runner.md Documents private networking placement.

Comment thread github/resource_github_actions_runner_group.go
Comment thread github/resource_github_enterprise_actions_runner_group.go
Comment thread github/resource_github_actions_runner_group.go
Comment thread github/resource_github_actions_runner_group.go
Comment thread templates/resources/enterprise_network_configuration.md.tmpl Outdated
Comment thread templates/resources/actions_hosted_runner.md.tmpl Outdated
Comment thread github/resource_github_organization_network_configuration.go
Comment thread github/resource_github_organization_network_configuration.go Outdated
@GrantBirki

Copy link
Copy Markdown

bump @austenstone we would love to see this land! ❤️

austenstone and others added 5 commits September 10, 2026 14:32
Preserve the upstream ETag diff handler alongside the networking diff handler instead of declaring CustomizeDiff twice.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Only treat HTTP 304 as a cache hit. Preserve 404 state removal and surface authorization/server failures instead of silently retaining stale networking state.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use API-created network configuration fixtures for runner-group acceptance tests, verify stable IDs on association, and exercise native create/update payloads and refreshed state offline.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve ETag suppression and in-place association updates. Correct descriptions to identify the SDK null-serialization limitation, not an API limitation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
austenstone and others added 2 commits September 10, 2026 15:11
Use default schema-generated documentation and import examples, validate network settings IDs, share state helpers, and cover computed timestamp refresh and import lifecycles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@austenstone austenstone changed the title feat: add enterprise network configuration support feat: add hosted runner private networking support Sep 10, 2026
@austenstone

Copy link
Copy Markdown
Contributor Author

Can we keep the configuration resources and runner-group attachment together here? They cover one end-to-end workflow; the unrelated runner-group refactors are gone.

The non-SDK inline feedback is addressed. CI and CodeQL need maintainer approval. Native detachment remains blocked on google/go-github#4541 and its release; replacement on removal is not the intended merge-ready behavior.

@@ -0,0 +1,4 @@
set -euo pipefail

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set -euo pipefail

Comment on lines +1 to +2
set -euo pipefail

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set -euo pipefail

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still undone

Comment on lines +276 to +278
if err = d.Set("network_configuration_id", runnerGroup.GetNetworkConfigurationID()); err != nil {
return err
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: this needs to be Computed

{
Config: configWithNetworking,
ConfigStateChecks: []statecheck.StateCheck{
sameID.AddStateValue(resourceName, tfjsonpath.New("id")),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what's the purpose of this check?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: github_enterprise_actions_runner_group / github_actions_runner_group - Support network configuration

7 participants