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