0
Git sync push
One script reply has been approved by the moderators Verified

Trigger this action after merging, to push changes to windmill

Created by hugo697 137 days ago Viewed 8723 times
0
Submitted by hugo697 Bun
Verified 137 days ago
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"); // needed in case the asset branch already exists in the repo
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