Skip to content

Commit c608f19

Browse files
Merge remote-tracking branch 'origin/cost-centers' into release-v6.9
2 parents f1cda3e + 2f2c50c commit c608f19

File tree

93 files changed

+2304
-441
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+2304
-441
lines changed

.github/workflows/ci.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: GitHub Actions CI
1+
name: CI
22

33
on:
44
push:
@@ -21,11 +21,11 @@ jobs:
2121
ci:
2222
name: Continuous Integration
2323
runs-on: ubuntu-latest
24+
permissions:
25+
contents: read
2426
defaults:
2527
run:
2628
shell: bash
27-
env:
28-
GITHUB_TEST_ORGANIZATION: kfcampbell-terraform-provider
2929
steps:
3030
- name: Checkout
3131
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
@@ -35,7 +35,7 @@ jobs:
3535
go-version-file: go.mod
3636
cache: true
3737
- run: make tools
38-
- run: make lint
38+
- run: make lintcheck
3939
- run: make website-lint
4040
- run: make build
4141
- run: make test

GNUmakefile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ tools:
88
go install github.com/client9/misspell/cmd/misspell@v0.3.4
99
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0
1010

11-
build: fmtcheck
11+
build: lintcheck
1212
CGO_ENABLED=0 go build -ldflags="-s -w" ./...
1313

1414
fmt:
1515
@echo "==> Fixing source code with golangci-lint..."
1616
golangci-lint fmt ./...
1717

18-
fmtcheck:
19-
@sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
20-
2118
lint:
19+
@echo "==> Checking source code against linters and fixing..."
20+
golangci-lint run --fix ./...
21+
22+
lintcheck:
2223
@echo "==> Checking source code against linters..."
2324
golangci-lint run ./...
2425

@@ -55,4 +56,4 @@ ifeq (,$(wildcard $(GOPATH)/src/$(WEBSITE_REPO)))
5556
endif
5657
@$(MAKE) -C $(GOPATH)/src/$(WEBSITE_REPO) website-provider-test PROVIDER_PATH=$(shell pwd) PROVIDER_NAME=$(PKG_NAME)
5758

58-
.PHONY: build test testacc fmt fmtcheck lint tools test-compile website website-lint website-test
59+
.PHONY: build test testacc fmt lint lintcheck tools test-compile website website-lint website-test

examples/cost_centers/main.tf

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
terraform {
2+
required_providers {
3+
github = {
4+
source = "integrations/github"
5+
version = "~> 6.0"
6+
}
7+
}
8+
}
9+
10+
provider "github" {
11+
token = var.github_token
12+
}
13+
14+
variable "github_token" {
15+
description = "GitHub classic personal access token (PAT) for an enterprise admin"
16+
type = string
17+
sensitive = true
18+
}
19+
20+
variable "enterprise_slug" {
21+
description = "The GitHub Enterprise slug"
22+
type = string
23+
}
24+
25+
variable "cost_center_name" {
26+
description = "Name for the cost center"
27+
type = string
28+
}
29+
30+
variable "users" {
31+
description = "Usernames to assign to the cost center"
32+
type = list(string)
33+
default = []
34+
}
35+
36+
variable "organizations" {
37+
description = "Organization logins to assign to the cost center"
38+
type = list(string)
39+
default = []
40+
}
41+
42+
variable "repositories" {
43+
description = "Repositories (full name, e.g. org/repo) to assign to the cost center"
44+
type = list(string)
45+
default = []
46+
}
47+
48+
resource "github_enterprise_cost_center" "example" {
49+
enterprise_slug = var.enterprise_slug
50+
name = var.cost_center_name
51+
}
52+
53+
# Authoritative assignments: Terraform will add/remove to match these lists.
54+
resource "github_enterprise_cost_center_resources" "example" {
55+
enterprise_slug = var.enterprise_slug
56+
cost_center_id = github_enterprise_cost_center.example.id
57+
58+
users = var.users
59+
organizations = var.organizations
60+
repositories = var.repositories
61+
}
62+
63+
data "github_enterprise_cost_center" "by_id" {
64+
enterprise_slug = var.enterprise_slug
65+
cost_center_id = github_enterprise_cost_center.example.id
66+
67+
depends_on = [github_enterprise_cost_center_resources.example]
68+
}
69+
70+
data "github_enterprise_cost_centers" "active" {
71+
enterprise_slug = var.enterprise_slug
72+
state = "active"
73+
74+
depends_on = [github_enterprise_cost_center.example]
75+
}
76+
77+
output "cost_center" {
78+
description = "Created cost center"
79+
value = {
80+
id = github_enterprise_cost_center.example.id
81+
name = github_enterprise_cost_center.example.name
82+
state = github_enterprise_cost_center.example.state
83+
azure_subscription = github_enterprise_cost_center.example.azure_subscription
84+
}
85+
}
86+
87+
output "cost_center_resources" {
88+
description = "Effective assignments (read from API)"
89+
value = {
90+
users = sort(tolist(github_enterprise_cost_center_resources.example.users))
91+
organizations = sort(tolist(github_enterprise_cost_center_resources.example.organizations))
92+
repositories = sort(tolist(github_enterprise_cost_center_resources.example.repositories))
93+
}
94+
}
95+
96+
output "cost_center_from_data_source" {
97+
description = "Cost center fetched by data source"
98+
value = {
99+
id = data.github_enterprise_cost_center.by_id.cost_center_id
100+
name = data.github_enterprise_cost_center.by_id.name
101+
state = data.github_enterprise_cost_center.by_id.state
102+
}
103+
}

github/apps.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"io"
1010
"net/http"
1111
"net/url"
12-
"path"
1312
"time"
1413

1514
"github.com/go-jose/go-jose/v3"
@@ -18,29 +17,22 @@ import (
1817

1918
// GenerateOAuthTokenFromApp generates a GitHub OAuth access token from a set of valid GitHub App credentials.
2019
// The returned token can be used to interact with both GitHub's REST and GraphQL APIs.
21-
func GenerateOAuthTokenFromApp(baseURL *url.URL, appID, appInstallationID, pemData string) (string, error) {
20+
func GenerateOAuthTokenFromApp(apiURL *url.URL, appID, appInstallationID, pemData string) (string, error) {
2221
appJWT, err := generateAppJWT(appID, time.Now(), []byte(pemData))
2322
if err != nil {
2423
return "", err
2524
}
2625

27-
token, err := getInstallationAccessToken(baseURL, appJWT, appInstallationID)
26+
token, err := getInstallationAccessToken(apiURL, appJWT, appInstallationID)
2827
if err != nil {
2928
return "", err
3029
}
3130

3231
return token, nil
3332
}
3433

35-
func getInstallationAccessToken(baseURL *url.URL, jwt, installationID string) (string, error) {
36-
hostname := baseURL.Hostname()
37-
if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) {
38-
baseURL.Path = path.Join(baseURL.Path, "api/v3/")
39-
}
40-
41-
baseURL.Path = path.Join(baseURL.Path, "app/installations/", installationID, "access_tokens")
42-
43-
req, err := http.NewRequest(http.MethodPost, baseURL.String(), nil)
34+
func getInstallationAccessToken(apiURL *url.URL, jwt, installationID string) (string, error) {
35+
req, err := http.NewRequest(http.MethodPost, apiURL.JoinPath("app/installations", installationID, "access_tokens").String(), nil)
4436
if err != nil {
4537
return "", err
4638
}

github/apps_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func TestGetInstallationAccessToken(t *testing.T) {
146146

147147
ts := githubApiMock([]*mockResponse{
148148
{
149-
ExpectedUri: fmt.Sprintf("/api/v3/app/installations/%s/access_tokens", testGitHubAppInstallationID),
149+
ExpectedUri: fmt.Sprintf("/app/installations/%s/access_tokens", testGitHubAppInstallationID),
150150
ExpectedHeaders: map[string]string{
151151
"Accept": "application/vnd.github.v3+json",
152152
"Authorization": fmt.Sprintf("Bearer %s", fakeJWT),

github/config.go

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"net/http"
77
"net/url"
8-
"path"
98
"regexp"
109
"strings"
1110
"time"
@@ -19,7 +18,8 @@ import (
1918
type Config struct {
2019
Token string
2120
Owner string
22-
BaseURL string
21+
BaseURL *url.URL
22+
IsGHES bool
2323
Insecure bool
2424
WriteDelay time.Duration
2525
ReadDelay time.Duration
@@ -38,12 +38,23 @@ type Owner struct {
3838
IsOrganization bool
3939
}
4040

41-
// DotComHost is the hostname for GitHub.com API.
42-
const DotComHost = "api.github.com"
41+
const (
42+
// DotComAPIURL is the base API URL for github.com.
43+
DotComAPIURL = "https://api.github.com/"
44+
// DotComHost is the hostname for github.com.
45+
DotComHost = "github.com"
46+
// DotComAPIHost is the API hostname for github.com.
47+
DotComAPIHost = "api.github.com"
48+
// GHESRESTAPISuffix is the rest api suffix for GitHub Enterprise Server.
49+
GHESRESTAPIPath = "api/v3/"
50+
)
4351

44-
// GHECDataResidencyHostMatch is a regex to match a GitHub Enterprise Cloud data residency host:
45-
// https://[hostname].ghe.com/ instances expect paths that behave similar to GitHub.com, not GitHub Enterprise Server.
46-
var GHECDataResidencyHostMatch = regexp.MustCompile(`^[a-zA-Z0-9.\-]+\.ghe\.com\/?$`)
52+
var (
53+
// GHECHostMatch is a regex to match GitHub Enterprise Cloud hosts.
54+
GHECHostMatch = regexp.MustCompile(`\.ghe\.com$`)
55+
// GHECAPIHostMatch is a regex to match GitHub Enterprise Cloud API hosts.
56+
GHECAPIHostMatch = regexp.MustCompile(`^api\.[a-zA-Z0-9-]+\.ghe\.com$`)
57+
)
4758

4859
func RateLimitedHTTPClient(client *http.Client, writeDelay, readDelay, retryDelay time.Duration, parallelRequests bool, retryableErrors map[int]bool, maxRetries int) *http.Client {
4960
client.Transport = NewEtagTransport(client.Transport)
@@ -81,38 +92,24 @@ func (c *Config) AnonymousHTTPClient() *http.Client {
8192
}
8293

8394
func (c *Config) NewGraphQLClient(client *http.Client) (*githubv4.Client, error) {
84-
uv4, err := url.Parse(c.BaseURL)
85-
if err != nil {
86-
return nil, err
87-
}
88-
89-
hostname := uv4.Hostname()
90-
if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) {
91-
uv4.Path = path.Join(uv4.Path, "api/graphql/")
95+
var path string
96+
if c.IsGHES {
97+
path = "api/graphql"
9298
} else {
93-
uv4.Path = path.Join(uv4.Path, "graphql")
99+
path = "graphql"
94100
}
95101

96-
return githubv4.NewEnterpriseClient(uv4.String(), client), nil
102+
return githubv4.NewEnterpriseClient(c.BaseURL.JoinPath(path).String(), client), nil
97103
}
98104

99105
func (c *Config) NewRESTClient(client *http.Client) (*github.Client, error) {
100-
uv3, err := url.Parse(c.BaseURL)
101-
if err != nil {
102-
return nil, err
106+
path := ""
107+
if c.IsGHES {
108+
path = GHESRESTAPIPath
103109
}
104110

105-
hostname := uv3.Hostname()
106-
if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) {
107-
uv3.Path = fmt.Sprintf("%s/", path.Join(uv3.Path, "api/v3"))
108-
}
109-
110-
v3client, err := github.NewClient(client).WithEnterpriseURLs(uv3.String(), "")
111-
if err != nil {
112-
return nil, err
113-
}
114-
115-
v3client.BaseURL = uv3
111+
v3client := github.NewClient(client)
112+
v3client.BaseURL = c.BaseURL.JoinPath(path)
116113

117114
return v3client, nil
118115
}
@@ -199,3 +196,45 @@ func (injector *previewHeaderInjectorTransport) RoundTrip(req *http.Request) (*h
199196
}
200197
return injector.rt.RoundTrip(req)
201198
}
199+
200+
// getBaseURL returns a correctly configured base URL and a bool as to if this is GitHub Enterprise Server.
201+
func getBaseURL(s string) (*url.URL, bool, error) {
202+
if len(s) == 0 {
203+
s = DotComAPIURL
204+
}
205+
206+
u, err := url.Parse(s)
207+
if err != nil {
208+
return nil, false, err
209+
}
210+
211+
if !u.IsAbs() {
212+
return nil, false, fmt.Errorf("base url must be absolute")
213+
}
214+
215+
u = u.JoinPath("/")
216+
217+
switch {
218+
case u.Host == DotComAPIHost:
219+
case u.Host == DotComHost:
220+
u.Host = DotComAPIHost
221+
case GHECAPIHostMatch.MatchString(u.Host):
222+
case GHECHostMatch.MatchString(u.Host):
223+
u.Host = fmt.Sprintf("api.%s", u.Host)
224+
default:
225+
u.Path = strings.TrimSuffix(u.Path, GHESRESTAPIPath)
226+
return u, true, nil
227+
}
228+
229+
if u.Scheme != "https" {
230+
return nil, false, fmt.Errorf("base url for github.com or ghe.com must use the https scheme")
231+
}
232+
233+
if len(u.Path) > 1 {
234+
return nil, false, fmt.Errorf("base url for github.com or ghe.com must not contain a path, got %s", u.Path)
235+
}
236+
237+
u.Path = "/"
238+
239+
return u, false, nil
240+
}

0 commit comments

Comments
 (0)