@@ -24,7 +24,11 @@ import (
24
24
sdkTrace "go.opentelemetry.io/otel/sdk/trace"
25
25
)
26
26
27
- const expirationLayout = "2006-01-02 15:04:05 MST"
27
+ const (
28
+ // Sometimes Github returns an abbreviated timezone name, sometimes a numeric offset 🙄
29
+ abbrevLayout = "2006-01-02 15:04:05 MST"
30
+ offsetLayout = "2006-01-02 15:04:05 -0700"
31
+ )
28
32
29
33
var flags struct {
30
34
TokenEnvVars string `name:"token-env-vars" help:"Comma-separated list of token env var(s)"`
@@ -154,33 +158,31 @@ func checkToken(ctx context.Context, name, token string) (happy bool, err error)
154
158
}
155
159
span .End ()
156
160
}()
161
+
157
162
fmt .Printf ("Checking %q...\n " , name )
158
163
164
+ // Make request to e.g. 'https://api.github.com/user' with token
159
165
userURL := flags .BaseURL .JoinPath ("user" ).String ()
160
-
161
166
req , err := http .NewRequestWithContext (ctx , "GET" , userURL , nil )
162
167
if err != nil {
163
168
return false , fmt .Errorf ("new request: %w" , err )
164
169
}
165
-
166
170
req .Header .Set ("Authorization" , "Bearer " + token )
167
-
168
171
resp , err := http .DefaultClient .Do (req )
169
172
if err != nil {
170
173
return false , fmt .Errorf ("do request: %w" , err )
171
174
}
172
175
defer resp .Body .Close ()
173
-
174
176
body , err := io .ReadAll (resp .Body )
175
177
if err != nil {
176
178
return false , fmt .Errorf ("reading body: %w" , err )
177
179
}
178
-
179
180
if resp .StatusCode != 200 {
180
181
span .SetAttributes (attribute .String ("ghtokmon.error_body" , strconv .QuoteToASCII (string (body [:1024 ]))))
181
182
return false , fmt .Errorf ("got status code %d != 200" , resp .StatusCode )
182
183
}
183
184
185
+ // Parse user login
184
186
var user struct {
185
187
Login string `json:"login"`
186
188
}
@@ -193,20 +195,26 @@ func checkToken(ctx context.Context, name, token string) (happy bool, err error)
193
195
194
196
happy = true
195
197
198
+ // Check token expiration
196
199
expirationValue := resp .Header .Get ("github-authentication-token-expiration" )
197
200
if expirationValue == "" {
198
201
fmt .Println ("Token expiration: NONE" )
199
202
} else {
200
203
span .SetAttributes (attribute .String ("ghtokmon.token.expiration" , expirationValue ))
201
- expiration , err := time .Parse (expirationLayout , expirationValue )
204
+
205
+ // Parse expiration timestamp
206
+ expiration , err := time .Parse (abbrevLayout , expirationValue )
207
+ if err != nil {
208
+ expiration , err = time .Parse (offsetLayout , expirationValue )
209
+ }
202
210
if err != nil {
203
211
return false , fmt .Errorf ("invalid expiration header value %q: %w" , expirationValue , err )
204
212
}
205
213
fmt .Printf ("Token expiration: %s" , expiration )
206
214
215
+ // Calculate time until expiration
207
216
expirationDuration := time .Until (expiration )
208
217
span .SetAttributes (attribute .Float64 ("ghtokmon.token.expiration_duration" , expirationDuration .Seconds ()))
209
-
210
218
fmt .Printf (" (%.1f days)\n " , expirationDuration .Hours ()/ 24 )
211
219
if expirationDuration < flags .ExpirationThreshold {
212
220
fmt .Println ("WARNING: Expiring soon!" )
@@ -216,6 +224,7 @@ func checkToken(ctx context.Context, name, token string) (happy bool, err error)
216
224
217
225
}
218
226
227
+ // Check rate limit usage
219
228
rateLimitLimit , _ := strconv .Atoi (resp .Header .Get ("x-ratelimit-limit" ))
220
229
if rateLimitLimit != 0 {
221
230
rateLimitUsed , _ := strconv .Atoi (resp .Header .Get ("x-ratelimit-used" ))
@@ -230,6 +239,7 @@ func checkToken(ctx context.Context, name, token string) (happy bool, err error)
230
239
}
231
240
}
232
241
242
+ // Get token permissions (sometimes helpful when rotating)
233
243
oAuthScopes := resp .Header .Get ("x-oauth-scopes" )
234
244
span .SetAttributes (attribute .String ("ghtokmon.token.oauth_scopes" , oAuthScopes ))
235
245
fmt .Printf ("OAuth scopes: %s\n \n " , oAuthScopes )
0 commit comments