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 292 days ago Picked 2 times
Submitted by hugo697 Bun
Verified 138 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