1 | import * as wmillclient from "windmill-client"; |
2 | import wmill from "windmill-cli"; |
3 | import { basename } from "node:path" |
4 | const util = require('util'); |
5 | const exec = util.promisify(require('child_process').exec); |
6 | import process from "process"; |
7 |
|
8 | export async function main( |
9 | payload: string, |
10 | skip_secret: boolean = false, |
11 | skip_variables: boolean = false, |
12 | skip_resources: boolean = false, |
13 | include_schedules: boolean = false, |
14 | include_users: boolean = false, |
15 | include_groups: boolean = false, |
16 | include_triggers: boolean = false, |
17 | include_settings: boolean = false, |
18 | include_key: boolean = false, |
19 | extra_includes: string = '', |
20 | excludes: string = '', |
21 | message: string = '' |
22 | ) { |
23 | const ws = process.env["WM_WORKSPACE"] |
24 | const gh_payload = JSON.parse(payload) |
25 | const gh_repo = gh_payload.repository?.full_name |
26 |
|
27 | console.log("Received git sync push request:") |
28 | console.log(`Repo: ${gh_repo}`) |
29 | console.log(`Ref: ${gh_payload.ref}`) |
30 | console.log(`Pusher: ${gh_payload.pusher.name}`) |
31 |
|
32 | const ws_settings = await wmillclient.WorkspaceService.getSettings({ workspace: ws }); |
33 | const repos = ws_settings.git_sync?.repositories |
34 |
|
35 | if (repos && repos.length > 0) { |
36 | for (const repo of repos) { |
37 | const repo_resource = await wmillclient.getResource(repo.git_repo_resource_path.split("$res:").pop()); |
38 | if (repo_resource.url?.includes(gh_repo)) { |
39 | console.log("Repo found...") |
40 | const cwd = process.cwd(); |
41 | process.env["HOME"] = "." |
42 |
|
43 | await git_clone(cwd, repo_resource, true); |
44 |
|
45 | await wmill_sync_push( |
46 | ws, |
47 | skip_secret, |
48 | skip_variables, |
49 | skip_resources, |
50 | include_schedules, |
51 | include_users, |
52 | include_groups, |
53 | include_triggers, |
54 | include_settings, |
55 | include_key, |
56 | extra_includes, |
57 | excludes, |
58 | message |
59 | ) |
60 | console.log("Finished syncing"); |
61 | process.chdir(`${cwd}`); |
62 | return { success: true } |
63 | } |
64 | } |
65 | } |
66 | } |
67 |
|
68 | async function git_clone( |
69 | cwd: string, |
70 | repo_resource: any, |
71 | use_individual_branch: boolean |
72 | ): Promise<string> { |
73 | let repo_url = repo_resource.url; |
74 | const subfolder = repo_resource.folder ?? ""; |
75 | const branch = repo_resource.branch ?? ""; |
76 | const repo_name = basename(repo_url, ".git"); |
77 | const azureMatch = repo_url.match(/AZURE_DEVOPS_TOKEN\((?<url>.+)\)/); |
78 | if (azureMatch) { |
79 | console.log( |
80 | "Requires Azure DevOps service account access token, requesting..." |
81 | ); |
82 | const azureResource = await wmillclient.getResource(azureMatch.groups.url); |
83 | const response = await fetch( |
84 | `https://login.microsoftonline.com/${azureResource.azureTenantId}/oauth2/token`, |
85 | { |
86 | method: "POST", |
87 | body: new URLSearchParams({ |
88 | client_id: azureResource.azureClientId, |
89 | client_secret: azureResource.azureClientSecret, |
90 | grant_type: "client_credentials", |
91 | resource: "499b84ac-1321-427f-aa17-267ca6975798/.default", |
92 | }), |
93 | } |
94 | ); |
95 | const { access_token } = await response.json(); |
96 | repo_url = repo_url.replace(azureMatch[0], access_token); |
97 | } |
98 | const args = ["clone", "--quiet", "--depth", "1"]; |
99 | if (use_individual_branch) { |
100 | args.push("--no-single-branch"); |
101 | } |
102 | if (subfolder !== "") { |
103 | args.push("--sparse"); |
104 | } |
105 | if (branch !== "") { |
106 | args.push("--branch"); |
107 | args.push(branch); |
108 | } |
109 | args.push(repo_url); |
110 | args.push(repo_name); |
111 | await sh_run(-1, "git", ...args); |
112 | try { |
113 | process.chdir(`${cwd}/${repo_name}`); |
114 | } catch (err) { |
115 | console.log( |
116 | `Error changing directory to '${cwd}/${repo_name}'. Error was:\n${err}` |
117 | ); |
118 | throw err; |
119 | } |
120 | process.chdir(`${cwd}/${repo_name}`); |
121 | if (subfolder !== "") { |
122 | await sh_run(undefined, "git", "sparse-checkout", "add", subfolder); |
123 | } |
124 | try { |
125 | process.chdir(`${cwd}/${repo_name}/${subfolder}`); |
126 | } catch (err) { |
127 | console.log( |
128 | `Error changing directory to '${cwd}/${repo_name}/${subfolder}'. Error was:\n${err}` |
129 | ); |
130 | throw err; |
131 | } |
132 | return repo_name; |
133 | } |
134 |
|
135 | async function sh_run( |
136 | secret_position: number | undefined, |
137 | cmd: string, |
138 | ...args: string[] |
139 | ) { |
140 | const nargs = secret_position != undefined ? args.slice() : args; |
141 | if (secret_position && secret_position < 0) { |
142 | secret_position = nargs.length - 1 + secret_position; |
143 | } |
144 | let secret: string | undefined = undefined |
145 | if (secret_position != undefined) { |
146 | nargs[secret_position] = "***"; |
147 | secret = args[secret_position] |
148 | } |
149 |
|
150 | console.log(`Running '${cmd} ${nargs.join(" ")} ...'`); |
151 | const command = exec(`${cmd} ${args.join(" ")}`) |
152 | try { |
153 | const { stdout, stderr } = await command |
154 | if (stdout.length > 0) { |
155 | console.log(stdout); |
156 | } |
157 | if (stderr.length > 0) { |
158 | console.log(stderr); |
159 | } |
160 | console.log("Command successfully executed"); |
161 | return stdout; |
162 |
|
163 | } catch (error) { |
164 | let errorString = error.toString(); |
165 | if (secret) { |
166 | errorString = errorString.replace(secret, "***"); |
167 | } |
168 | const err = `SH command '${cmd} ${nargs.join( |
169 | " " |
170 | )}' returned with error ${errorString}`; |
171 | throw Error(err); |
172 | } |
173 | } |
174 |
|
175 |
|
176 | async function wmill_sync_push( |
177 | workspace_id: string, |
178 | skip_secret: boolean, |
179 | skip_variables: boolean, |
180 | skip_resources: boolean, |
181 | include_schedules: boolean, |
182 | include_users: boolean, |
183 | include_groups: boolean, |
184 | include_triggers: boolean, |
185 | include_settings: boolean, |
186 | include_key: boolean, |
187 | extra_includes: string, |
188 | excludes: string, |
189 | message: string, |
190 | ) { |
191 | console.log("Pushing workspace to windmill"); |
192 | await wmill_run( |
193 | 3, |
194 | "sync", |
195 | "push", |
196 | "--token", |
197 | process.env["WM_TOKEN"] ?? "", |
198 | "--workspace", |
199 | workspace_id, |
200 | "--yes", |
201 | skip_secret ? "--skip-secrets" : "", |
202 | skip_variables ? "--skip-variables" : "", |
203 | skip_resources ? "--skip-resources" : "", |
204 | include_schedules ? "--include-schedules" : "", |
205 | include_users ? "--include-users" : "", |
206 | include_groups ? "--include-groups" : "", |
207 | include_triggers ? "--include-triggers" : "", |
208 | include_settings ? "--include-settings" : "", |
209 | include_key ? "include-key" : "", |
210 | "--base-url", |
211 | process.env["BASE_URL"] + "/", |
212 | extra_includes ? "--extra-includes" : "", |
213 | extra_includes ? extra_includes : "", |
214 | excludes ? "--excludes" : "", |
215 | excludes ? excludes : "", |
216 | message ? "--message" : "", |
217 | message ? message : "", |
218 | ); |
219 | } |
220 |
|
221 | async function wmill_run(secret_position: number, ...cmd: string[]) { |
222 | cmd = cmd.filter((elt) => elt !== ""); |
223 | const cmd2 = cmd.slice(); |
224 | cmd2[secret_position] = "***"; |
225 | console.log(`Running 'wmill ${cmd2.join(" ")} ...'`); |
226 | await wmill.parse(cmd); |
227 | console.log("Command successfully executed"); |
228 | } |
229 |
|
230 |
|