Skip to content

Commit 1558730

Browse files
authored
add:app_auth (#5)
1 parent 4d37f19 commit 1558730

File tree

6 files changed

+291
-24
lines changed

6 files changed

+291
-24
lines changed

docs/index.md

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,55 @@
1-
---
2-
# generated by https://github.com/hashicorp/terraform-plugin-docs
3-
page_title: "kwgithub Provider"
4-
description: |-
5-
The KW GitHub provider is used to interact with GitHub resources specific to Knowledge Work organization.
6-
---
7-
8-
# kwgithub Provider
1+
# KW GitHub Provider
92

103
The KW GitHub provider is used to interact with GitHub resources specific to Knowledge Work organization.
114

12-
## Example Usage
5+
## Authentication
6+
7+
The provider supports two authentication methods:
8+
9+
### Personal Access Token
1310

1411
```terraform
1512
provider "kwgithub" {
1613
token = var.github_token
1714
}
1815
```
1916

20-
<!-- schema generated by tfplugindocs -->
21-
## Schema
17+
### GitHub App Installation
18+
19+
```terraform
20+
provider "kwgithub" {
21+
app_auth {
22+
id = var.github_app_id
23+
installation_id = var.github_app_installation_id
24+
pem_file = var.github_app_pem_file
25+
}
26+
}
27+
```
28+
29+
## Configuration
30+
31+
### Arguments
32+
33+
* `token` - (Optional) GitHub personal access token. Can also be set via `GITHUB_TOKEN` environment variable.
34+
* `github_base_url` - (Optional) GitHub base URL. Defaults to https://api.github.com. Can also be set via `GITHUB_BASE_URL` environment variable.
35+
* `app_auth` - (Optional) Configuration block to use GitHub App installation token. Conflicts with `token`.
36+
* `id` - (Required) GitHub App ID. Can also be set via `GITHUB_APP_ID` environment variable.
37+
* `installation_id` - (Required) GitHub App installation ID. Can also be set via `GITHUB_APP_INSTALLATION_ID` environment variable.
38+
* `pem_file` - (Required) GitHub App private key PEM file contents. Can also be set via `GITHUB_APP_PEM_FILE` environment variable.
39+
40+
### Environment Variables
41+
42+
When using environment variables, an empty `app_auth` block is required to allow provider configurations from environment variables to be specified:
43+
44+
```terraform
45+
provider "kwgithub" {
46+
app_auth {}
47+
}
48+
```
2249

23-
### Optional
50+
Set the following environment variables:
51+
- `GITHUB_APP_ID`
52+
- `GITHUB_APP_INSTALLATION_ID`
53+
- `GITHUB_APP_PEM_FILE`
2454

25-
- `github_base_url` (String) GitHub base URL. Defaults to https://api.github.com. Can also be set via GITHUB_BASE_URL environment variable.
26-
- `token` (String, Sensitive) GitHub personal access token. Can also be set via GITHUB_TOKEN environment variable.
55+
Note: If you have a PEM file on disk, you can pass it in via `pem_file = file("path/to/file.pem")`.

examples/provider/provider.tf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
provider "kwgithub" {
22
token = var.github_token
3+
}
4+
5+
# GitHub App authentication example
6+
provider "kwgithub" {
7+
app_auth {
8+
id = var.github_app_id
9+
installation_id = var.github_app_installation_id
10+
pem_file = var.github_app_pem_file
11+
}
312
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ module github.com/knowledge-work/terraform-provider-kw-github
33
go 1.23.0
44

55
require (
6+
github.com/go-jose/go-jose/v3 v3.0.4
67
github.com/google/go-github/v74 v74.0.0
78
github.com/hashicorp/terraform-plugin-framework v1.15.1
9+
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0
810
github.com/hashicorp/terraform-plugin-go v0.28.0
911
)
1012

@@ -25,6 +27,7 @@ require (
2527
github.com/oklog/run v1.0.0 // indirect
2628
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
2729
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
30+
golang.org/x/crypto v0.38.0 // indirect
2831
golang.org/x/net v0.40.0 // indirect
2932
golang.org/x/sys v0.33.0 // indirect
3033
golang.org/x/text v0.26.0 // indirect

go.sum

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
66
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
77
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
88
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
9+
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
10+
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
911
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
1012
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
1113
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
1214
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
1315
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
1416
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
1517
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
18+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1619
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
1720
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1821
github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM=
@@ -29,6 +32,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
2932
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
3033
github.com/hashicorp/terraform-plugin-framework v1.15.1 h1:2mKDkwb8rlx/tvJTlIcpw0ykcmvdWv+4gY3SIgk8Pq8=
3134
github.com/hashicorp/terraform-plugin-framework v1.15.1/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI=
35+
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw=
36+
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0/go.mod h1:lZvZvagw5hsJwuY7mAY6KUz45/U6fiDR0CzQAwWD0CA=
3237
github.com/hashicorp/terraform-plugin-go v0.28.0 h1:zJmu2UDwhVN0J+J20RE5huiF3XXlTYVIleaevHZgKPA=
3338
github.com/hashicorp/terraform-plugin-go v0.28.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o=
3439
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
@@ -57,13 +62,15 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ
5762
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5863
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5964
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
65+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6066
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
6167
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
6268
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
6369
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
6470
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
6571
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
6672
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
73+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
6774
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
6875
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
6976
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
@@ -76,19 +83,58 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce
7683
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
7784
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
7885
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
86+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
87+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
88+
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
89+
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
90+
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
91+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
92+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
93+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
94+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
95+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
96+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
97+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
7998
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
8099
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
100+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
101+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
102+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
103+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
81104
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
82105
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
106+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
107+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
83108
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
84109
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
85110
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
111+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
112+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
86113
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
114+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
87115
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
116+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
117+
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
88118
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
89119
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
120+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
121+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
122+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
123+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
124+
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
125+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
126+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
127+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
128+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
129+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
130+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
90131
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
91132
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
133+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
134+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
135+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
136+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
137+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
92138
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
93139
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
94140
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
@@ -97,5 +143,6 @@ google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3i
97143
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
98144
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
99145
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
146+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
100147
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
101148
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/githubclient/client.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
package githubclient
22

33
import (
4+
"crypto/rsa"
5+
"crypto/x509"
6+
"encoding/json"
7+
"encoding/pem"
8+
"errors"
9+
"fmt"
10+
"io"
411
"net/http"
12+
"strconv"
13+
"time"
514

15+
"github.com/go-jose/go-jose/v3"
16+
"github.com/go-jose/go-jose/v3/jwt"
617
"github.com/google/go-github/v74/github"
718
)
819

@@ -19,3 +30,106 @@ func NewClient(token, baseURL string) *Client {
1930
client, _ := github.NewClient(tc).WithEnterpriseURLs(baseURL, baseURL)
2031
return &Client{client}
2132
}
33+
34+
func NewClientWithApp(appID, installationID, pemFile, baseURL string) *Client {
35+
token, err := GenerateOAuthTokenFromApp(baseURL, appID, installationID, pemFile)
36+
if err != nil {
37+
return nil
38+
}
39+
40+
tc := github.NewClient(nil).WithAuthToken(token).Client()
41+
client, _ := github.NewClient(tc).WithEnterpriseURLs(baseURL, baseURL)
42+
return &Client{client}
43+
}
44+
45+
func GenerateOAuthTokenFromApp(baseURL, appID, appInstallationID, pemData string) (string, error) {
46+
appJWT, err := generateAppJWT(appID, time.Now(), []byte(pemData))
47+
if err != nil {
48+
return "", err
49+
}
50+
51+
token, err := getInstallationAccessToken(baseURL, appJWT, appInstallationID)
52+
if err != nil {
53+
return "", err
54+
}
55+
56+
return token, nil
57+
}
58+
59+
func generateAppJWT(appID string, issuedAt time.Time, privateKeyPEM []byte) (string, error) {
60+
block, _ := pem.Decode(privateKeyPEM)
61+
if block == nil {
62+
return "", errors.New("failed to parse PEM block containing the key")
63+
}
64+
65+
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
66+
if err != nil {
67+
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
68+
if err != nil {
69+
return "", fmt.Errorf("failed to parse private key: %v", err)
70+
}
71+
var ok bool
72+
privateKey, ok = parsedKey.(*rsa.PrivateKey)
73+
if !ok {
74+
return "", errors.New("private key is not RSA")
75+
}
76+
}
77+
78+
appIDInt, err := strconv.Atoi(appID)
79+
if err != nil {
80+
return "", fmt.Errorf("invalid app ID: %v", err)
81+
}
82+
83+
claims := jwt.Claims{
84+
Issuer: strconv.Itoa(appIDInt),
85+
IssuedAt: jwt.NewNumericDate(issuedAt),
86+
Expiry: jwt.NewNumericDate(issuedAt.Add(10 * time.Minute)),
87+
}
88+
89+
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privateKey}, nil)
90+
if err != nil {
91+
return "", fmt.Errorf("failed to create signer: %v", err)
92+
}
93+
94+
token, err := jwt.Signed(signer).Claims(claims).CompactSerialize()
95+
if err != nil {
96+
return "", fmt.Errorf("failed to sign token: %v", err)
97+
}
98+
99+
return token, nil
100+
}
101+
102+
func getInstallationAccessToken(baseURL, appJWT, installationID string) (string, error) {
103+
url := fmt.Sprintf("%s/app/installations/%s/access_tokens", baseURL, installationID)
104+
105+
req, err := http.NewRequest("POST", url, nil)
106+
if err != nil {
107+
return "", fmt.Errorf("failed to create request: %v", err)
108+
}
109+
110+
req.Header.Set("Authorization", "Bearer "+appJWT)
111+
req.Header.Set("Accept", "application/vnd.github.v3+json")
112+
req.Header.Set("User-Agent", "terraform-provider-kw-github")
113+
114+
client := &http.Client{Timeout: 30 * time.Second}
115+
resp, err := client.Do(req)
116+
if err != nil {
117+
return "", fmt.Errorf("failed to make request: %v", err)
118+
}
119+
defer resp.Body.Close()
120+
121+
if resp.StatusCode != http.StatusCreated {
122+
body, _ := io.ReadAll(resp.Body)
123+
return "", fmt.Errorf("failed to get access token: %s - %s", resp.Status, string(body))
124+
}
125+
126+
var tokenResp struct {
127+
Token string `json:"token"`
128+
}
129+
130+
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
131+
return "", fmt.Errorf("failed to decode response: %v", err)
132+
}
133+
134+
return tokenResp.Token, nil
135+
}

0 commit comments

Comments
 (0)