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