1
+ using System . Net . Http . Headers ;
2
+ using System . Text ;
3
+ using System . Text . Json ;
4
+ using System . Text . RegularExpressions ;
5
+ using Newtonsoft . Json ;
6
+
7
+ namespace TelegramBuildBot ;
8
+
9
+ public class Builder
10
+ {
11
+ public Builder ( string gitHubToken )
12
+ {
13
+ GitHubToken = gitHubToken ;
14
+ }
15
+
16
+ private static readonly Dictionary < string , string ? > RepoToWorkflow = new ( )
17
+ {
18
+ { "ethpandaops/armiarma" , "build-push-armiarma.yml" } ,
19
+ { "dapplion/beacon-metrics-gazer" , "build-push-beacon-metrics-gazer.yml" } ,
20
+ { "hyperledger/besu" , "build-push-besu.yml" } ,
21
+ { "ralexstokes/ethereum_consensus_monitor" , "build-push-consensus-monitor.yml" } ,
22
+ { "sigp/eleel" , "build-push-eleel.yml" } ,
23
+ { "ledgerwatch/erigon" , "build-push-erigon.yml" } ,
24
+ { "ethpandaops/ethereum-genesis-generator" , "build-push-genesis-generator.yml" } ,
25
+ { "ethereumjs/ethereumjs-monorepo" , "build-push-ethereumjs.yml" } ,
26
+ { "ethereum/nodemonitor" , "build-push-execution-monitor.yml" } ,
27
+ { "flashbots/builder" , "build-push-flashbots-builder.yml" } ,
28
+ { "ethereum/go-ethereum" , "build-push-geth.yml" } ,
29
+ { "ethpandaops/goomy-blob" , "build-push-goomy-blob.yml" } ,
30
+ { "migalabs/goteth" , "build-push-goteth.yml" } ,
31
+ { "grandinetech/grandine" , "build-push-grandine.yml" } ,
32
+ { "sigp/lighthouse" , "build-push-lighthouse.yml" } ,
33
+ { "chainsafe/lodestar" , "build-push-lodestar.yml" } ,
34
+ { "ralexstokes/mev-rs" , "build-push-mev-rs.yml" } ,
35
+ { "nethermindeth/nethermind" , "build-push-nethermind.yml" } ,
36
+ { "status-im/nimbus-eth1" , "build-push-nimbus-eth1.yml" } ,
37
+ { "status-im/nimbus-eth2" , "build-push-nimbus-eth2.yml" } ,
38
+ { "prysmaticlabs/prysm" , "build-push-prysm.yml" } ,
39
+ { "paradigmxyz/reth" , "build-push-reth.yml" } ,
40
+ { "consensys/teku" , "build-push-teku.yml" } ,
41
+ { "mariusvanderwijden/tx-fuzz" , "build-push-tx-fuzz.yml" } ,
42
+ } ;
43
+
44
+ public string GitHubToken { get ; set ; }
45
+
46
+ public async Task < string ? > ProcessMessage ( string messageText )
47
+ {
48
+
49
+ if ( messageText . StartsWith ( "/build" ) || messageText . StartsWith ( "/barnabas" ) )
50
+ {
51
+ string [ ] msgSegments = messageText . Split ( ' ' ) ;
52
+ if ( msgSegments . Length < 2 )
53
+ {
54
+ Console . WriteLine ( "No repo link supplied" ) ;
55
+ return "You need to supply a repository link with a branch\\ ." ;
56
+ }
57
+
58
+ string url = msgSegments [ 1 ] ;
59
+
60
+ var regex = new Regex ( @"https:\/\/github\.com\/(.*?)\/tree\/(.*)" ) ;
61
+ var match = regex . Match ( url ) ;
62
+
63
+ if ( match is { Success : true , Groups . Count : 3 } )
64
+ {
65
+ string ? repo = match . Groups [ 1 ] . Value ;
66
+ string branch = match . Groups [ 2 ] . Value ;
67
+ ( bool triggerSuccess , List < string > dockerImages , string runUrl ) = await TriggerGitHubWorkflow ( repo , branch ) ;
68
+ if ( triggerSuccess )
69
+ {
70
+ Console . WriteLine ( $ "Build triggered for { repo } /{ branch } [Run URL: { runUrl } | DockerImage: { String . Join ( ':' , dockerImages ) } ]") ;
71
+ return $ "Your build was triggered\\ . [View run on GitHub]({ runUrl } )\n Docker Image\\ (s\\ ) once run completed:\n { string . Join ( '\n ' , dockerImages . Select ( x => $ "`{ x } `") ) } ";
72
+ }
73
+
74
+ Console . WriteLine ( "Unable to trigger build." ) ;
75
+ return "Sorry\\ . Was unable to trigger your build\\ . Likely the repository is not supported\\ ." ;
76
+
77
+ }
78
+
79
+ Console . WriteLine ( "Unable to parse repo" ) ;
80
+ return "Sorry\\ . Was unable to get the repository and branch from the URL\\ . Check your URL and try again\\ ." ;
81
+ }
82
+
83
+ return null ;
84
+ }
85
+ private async Task < ( bool IsSuccessStatusCode , List < string > dockerImageUrls , string ? runUrl ) > TriggerGitHubWorkflow ( string ? repo , string branch )
86
+ {
87
+
88
+ // Register a runner with github
89
+ using HttpClient client = new ( ) ;
90
+ client . DefaultRequestHeaders . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/vnd.github+json" ) ) ;
91
+ client . DefaultRequestHeaders . Add ( "X-GitHub-Api-Version" , "2022-11-28" ) ;
92
+ client . DefaultRequestHeaders . Authorization = AuthenticationHeaderValue . Parse ( $ "Bearer { GitHubToken } ") ;
93
+ client . DefaultRequestHeaders . UserAgent . Add ( new ProductInfoHeaderValue ( "build-bot" , "1" ) ) ;
94
+
95
+ var requestData = new {
96
+ @ref = "master" , // Ref of the build repo
97
+ inputs = new {
98
+ repository = repo ,
99
+ @ref = branch
100
+ }
101
+ } ;
102
+
103
+ bool isFork = false ;
104
+
105
+ // Check if given repo is a direct map
106
+ if ( ! RepoToWorkflow . TryGetValue ( repo . ToLower ( ) , out string ? workflowId ) )
107
+ {
108
+ Console . WriteLine ( $ "Repo { repo } not in the workflow map. checking for fork.") ;
109
+
110
+ // check for fork
111
+ var forkResp = await client . GetAsync ( $ "https://api.github.com/repos/{ repo } ") ;
112
+ if ( forkResp . IsSuccessStatusCode )
113
+ {
114
+ // Got repo meta
115
+ string forkContent = await forkResp . Content . ReadAsStringAsync ( ) ;
116
+ JsonDocument forkJson = System . Text . Json . JsonSerializer . Deserialize < JsonDocument > ( forkContent ) ;
117
+
118
+ // check if fork
119
+ isFork = forkJson . RootElement . GetProperty ( "fork" ) . GetBoolean ( ) ;
120
+ if ( ! isFork )
121
+ {
122
+ // Not a fork - irgnore.
123
+ return ( false , null , String . Empty ) ! ;
124
+ }
125
+
126
+ string ? parentRepo = forkJson . RootElement . GetProperty ( "parent" ) . GetProperty ( "full_name" ) . GetString ( ) ;
127
+
128
+ // Check if we have a workflow for the parent
129
+ if ( ! RepoToWorkflow . TryGetValue ( parentRepo . ToLower ( ) , out workflowId ) )
130
+ {
131
+ return ( false , null , String . Empty ) ! ;
132
+ }
133
+
134
+ Console . WriteLine ( $ "Found valid parent repo at { parentRepo } ") ;
135
+ }
136
+ else
137
+ {
138
+ // Unable to grab repo meta - return error
139
+ return ( false , null , String . Empty ) ! ;
140
+ }
141
+
142
+ }
143
+
144
+ // Build tag url
145
+
146
+ // Grab the docker base from the workflow
147
+ string dockerBase = Regex . Match ( workflowId , @"build-push-(.+)\.yml" ) . Groups [ 1 ] . Value ;
148
+ List < string > dockerImageUrls = new ( ) ;
149
+
150
+ //string dockerhubPrefix = "{dockerhubPrefix}";
151
+ string dockerhubPrefix = "" ;
152
+ if ( dockerBase == "prysm" )
153
+ {
154
+ if ( isFork )
155
+ {
156
+ string forkUser = repo . Split ( '/' ) [ 0 ] ;
157
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/prysm-beacon-chain:{ forkUser } -{ branch } ") ;
158
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/prysm-validator:{ forkUser } -{ branch } ") ;
159
+ }
160
+ else
161
+ {
162
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/prysm-beacon-chain:{ branch } ") ;
163
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/prysm-validator:{ branch } ") ;
164
+ }
165
+ }
166
+ else if ( dockerBase == "nimbus-eth2" )
167
+ {
168
+ if ( isFork )
169
+ {
170
+ string forkUser = repo . Split ( '/' ) [ 0 ] ;
171
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/nimbus-eth2:{ forkUser } -{ branch } ") ;
172
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/nimbus-validator-client:{ forkUser } -{ branch } ") ;
173
+ }
174
+ else
175
+ {
176
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/nimbus-eth2:{ branch } ") ;
177
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/nimbus-validator-client:{ branch } ") ;
178
+ }
179
+
180
+ }
181
+ else
182
+ {
183
+ if ( isFork )
184
+ {
185
+ string forkUser = repo . Split ( '/' ) [ 0 ] ;
186
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/{ dockerBase } :{ forkUser } -{ branch } ") ;
187
+ }
188
+ else
189
+ {
190
+ dockerImageUrls . Add ( $ "{ dockerhubPrefix } ethpandaops/{ dockerBase } :{ branch } ") ;
191
+ }
192
+ }
193
+
194
+ // Trigger job
195
+ var content = new StringContent (
196
+ JsonConvert . SerializeObject ( requestData ) ,
197
+ Encoding . UTF8 ,
198
+ "application/json" ) ;
199
+
200
+ HttpResponseMessage response = await client . PostAsync (
201
+ $ "https://api.github.com/repos/ethpandaops/eth-client-docker-image-builder/actions/workflows/{ workflowId } /dispatches", content ) ;
202
+
203
+ // Grab job url from GH
204
+ await Task . Delay ( 1500 ) ;
205
+ HttpResponseMessage runsResponse = await client . GetAsync (
206
+ $ "https://api.github.com/repos/ethpandaops/eth-client-docker-image-builder/actions/runs") ;
207
+
208
+ string ? runUrl = String . Empty ;
209
+ if ( response . IsSuccessStatusCode )
210
+ {
211
+ string runsContent = await runsResponse . Content . ReadAsStringAsync ( ) ;
212
+ JsonDocument ? responseObject = System . Text . Json . JsonSerializer . Deserialize < JsonDocument > ( runsContent ) ;
213
+ if ( responseObject != null )
214
+ {
215
+ var firstRun = responseObject . RootElement . GetProperty ( "workflow_runs" ) . EnumerateArray ( ) . FirstOrDefault ( ) ;
216
+ runUrl = firstRun . GetProperty ( "html_url" ) . GetString ( ) ;
217
+ }
218
+ }
219
+
220
+ return ( response . IsSuccessStatusCode , dockerImageUrls , runUrl ) ;
221
+ }
222
+
223
+ }
0 commit comments