1
- import { Octokit } from '@octokit/rest' ;
2
1
import { AppAuth } from '@octokit/auth-app/dist-types/types' ;
3
- import { listRunners , terminateRunner , RunnerInfo } from './runners' ;
4
- import { createGithubAppAuth , createInstallationClient } from './scale-up' ;
5
- import { getIdleRunnerCount , ScalingDownConfig } from './scale-down-config' ;
6
- import yn from 'yn' ;
2
+ import { Octokit } from '@octokit/rest' ;
7
3
import moment from 'moment' ;
4
+ import yn from 'yn' ;
5
+ import { listRunners , RunnerInfo , terminateRunner } from './runners' ;
6
+ import { getIdleRunnerCount , ScalingDownConfig } from './scale-down-config' ;
7
+ import { createGithubAppAuth , createInstallationClient } from './scale-up' ;
8
8
9
9
async function createAppClient ( githubAppAuth : AppAuth ) : Promise < Octokit > {
10
10
const auth = await githubAppAuth ( { type : 'app' } ) ;
@@ -22,24 +22,75 @@ function getRepo(runner: RunnerInfo, orgLevel: boolean): Repo {
22
22
: { repoOwner : runner . repo ?. split ( '/' ) [ 0 ] as string , repoName : runner . repo ?. split ( '/' ) [ 1 ] as string } ;
23
23
}
24
24
25
- async function createGitHubClientForRunner ( runner : RunnerInfo , orgLevel : boolean ) : Promise < Octokit > {
26
- const githubClient = await createAppClient ( await createGithubAppAuth ( undefined ) ) ;
27
- const repo = getRepo ( runner , orgLevel ) ;
25
+ function createGitHubClientForRunnerFactory ( ) : ( runner : RunnerInfo , orgLevel : boolean ) => Promise < Octokit > {
26
+ const cache : Map < string , Octokit > = new Map ( ) ;
27
+
28
+ return async ( runner : RunnerInfo , orgLevel : boolean ) => {
29
+ const githubClient = await createAppClient ( await createGithubAppAuth ( undefined ) ) ;
30
+ const repo = getRepo ( runner , orgLevel ) ;
31
+ const key = orgLevel ? repo . repoOwner : repo . repoOwner + repo . repoName ;
32
+ const cachedOctokit = cache . get ( key ) ;
33
+
34
+ if ( cachedOctokit ) {
35
+ console . debug ( `[createGitHubClientForRunner] Cache hit for ${ key } ` ) ;
36
+ return cachedOctokit ;
37
+ }
38
+
39
+ console . debug ( `[createGitHubClientForRunner] Cache miss for ${ key } ` ) ;
40
+ const installationId = orgLevel
41
+ ? (
42
+ await githubClient . apps . getOrgInstallation ( {
43
+ org : repo . repoOwner ,
44
+ } )
45
+ ) . data . id
46
+ : (
47
+ await githubClient . apps . getRepoInstallation ( {
48
+ owner : repo . repoOwner ,
49
+ repo : repo . repoName ,
50
+ } )
51
+ ) . data . id ;
52
+ const octokit = await createInstallationClient ( await createGithubAppAuth ( installationId ) ) ;
53
+ cache . set ( key , octokit ) ;
54
+
55
+ return octokit ;
56
+ } ;
57
+ }
58
+
59
+ /**
60
+ * Extract the inner type of a promise if any
61
+ */
62
+ export type UnboxPromise < T > = T extends Promise < infer U > ? U : T ;
28
63
29
- const installationId = orgLevel
30
- ? (
31
- await githubClient . apps . getOrgInstallation ( {
64
+ type GhRunners = UnboxPromise < ReturnType < Octokit [ 'actions' ] [ 'listSelfHostedRunnersForRepo' ] > > [ 'data' ] [ 'runners' ] ;
65
+
66
+ function listGithubRunnersFactory ( ) : (
67
+ client : Octokit ,
68
+ runner : RunnerInfo ,
69
+ enableOrgLevel : boolean ,
70
+ ) => Promise < GhRunners > {
71
+ const cache : Map < string , GhRunners > = new Map ( ) ;
72
+ return async ( client : Octokit , runner : RunnerInfo , enableOrgLevel : boolean ) => {
73
+ const repo = getRepo ( runner , enableOrgLevel ) ;
74
+ const key = enableOrgLevel ? repo . repoOwner : repo . repoOwner + repo . repoName ;
75
+ const cachedRunners = cache . get ( key ) ;
76
+ if ( cachedRunners ) {
77
+ console . debug ( `[listGithubRunners] Cache hit for ${ key } ` ) ;
78
+ return cachedRunners ;
79
+ }
80
+
81
+ console . debug ( `[listGithubRunners] Cache miss for ${ key } ` ) ;
82
+ const runners = enableOrgLevel
83
+ ? await client . paginate ( client . actions . listSelfHostedRunnersForOrg , {
32
84
org : repo . repoOwner ,
33
85
} )
34
- ) . data . id
35
- : (
36
- await githubClient . apps . getRepoInstallation ( {
86
+ : await client . paginate ( client . actions . listSelfHostedRunnersForRepo , {
37
87
owner : repo . repoOwner ,
38
88
repo : repo . repoName ,
39
- } )
40
- ) . data . id ;
89
+ } ) ;
90
+ cache . set ( key , runners ) ;
41
91
42
- return createInstallationClient ( await createGithubAppAuth ( installationId ) ) ;
92
+ return runners ;
93
+ } ;
43
94
}
44
95
45
96
function runnerMinimumTimeExceeded ( runner : RunnerInfo , minimumRunningTimeInMinutes : string ) : boolean {
@@ -102,24 +153,20 @@ export async function scaleDown(): Promise<void> {
102
153
return ;
103
154
}
104
155
156
+ const createGitHubClientForRunner = createGitHubClientForRunnerFactory ( ) ;
157
+ const listGithubRunners = listGithubRunnersFactory ( ) ;
158
+
105
159
for ( const ec2runner of runners ) {
106
160
if ( ! runnerMinimumTimeExceeded ( ec2runner , minimumRunningTimeInMinutes ) ) {
107
161
continue ;
108
162
}
109
163
110
164
const githubAppClient = await createGitHubClientForRunner ( ec2runner , enableOrgLevel ) ;
111
- const repo = getRepo ( ec2runner , enableOrgLevel ) ;
112
- const registered = enableOrgLevel
113
- ? await githubAppClient . actions . listSelfHostedRunnersForOrg ( {
114
- org : repo . repoOwner ,
115
- } )
116
- : await githubAppClient . actions . listSelfHostedRunnersForRepo ( {
117
- owner : repo . repoOwner ,
118
- repo : repo . repoName ,
119
- } ) ;
120
165
166
+ const repo = getRepo ( ec2runner , enableOrgLevel ) ;
167
+ const ghRunners = await listGithubRunners ( githubAppClient , ec2runner , enableOrgLevel ) ;
121
168
let orphanEc2Runner = true ;
122
- for ( const ghRunner of registered . data . runners ) {
169
+ for ( const ghRunner of ghRunners ) {
123
170
const runnerName = ghRunner . name as string ;
124
171
if ( runnerName === ec2runner . instanceId ) {
125
172
orphanEc2Runner = false ;
0 commit comments