0
Git repo test read write
One script reply has been approved by the moderators Verified

This script will test the connection to the Git repo passed stored in the resources passed as an argument. It first clones the repo (checking read) and then attemps an empty push (checking write)

Created by hugo697 504 days ago Viewed 34907 times
0
Submitted by rubenfiszel Bun
Verified 228 days ago
1
import * as wmill from "windmill-client";
2
import { basename } from "node:path"
3
const util = require('util');
4
const exec = util.promisify(require('child_process').exec);
5

6

7
export async function main(repo_url_resource_path: string) {
8
  const cwd = process.cwd();
9
  process.env["HOME"] = ".";
10
  console.log(`Cloning repo from resource`);
11
  const repo_name = await git_clone(repo_url_resource_path);
12
  process.chdir(`${cwd}/${repo_name}`);
13
  console.log(`Attempting an empty push to repository ${repo_name}`);
14
  await git_push();
15
  console.log("Finished");
16
  process.chdir(`${cwd}`);
17
}
18

19
async function git_clone(repo_resource_path: string): Promise<string> {
20
  // TODO: handle private SSH keys as well
21

22
  const repo_resource = await wmill.getResource(repo_resource_path);
23

24
  let repo_url = repo_resource.url
25

26
  if (repo_resource.is_github_app) {
27
    const token = await get_gh_app_token()
28
    const authRepoUrl = prependTokenToGitHubUrl(repo_resource.url, token);
29
    repo_url = authRepoUrl;
30
  }
31

32
  const azureMatch = repo_url.match(/AZURE_DEVOPS_TOKEN\((?<url>.+)\)/);
33
  if (azureMatch) {
34
    console.log(
35
      "Requires Azure DevOps service account access token, requesting..."
36
    );
37
    const azureResource = await wmill.getResource(azureMatch.groups.url);
38
    const response = await fetch(
39
      `https://login.microsoftonline.com/${azureResource.azureTenantId}/oauth2/token`,
40
      {
41
        method: "POST",
42
        body: new URLSearchParams({
43
          client_id: azureResource.azureClientId,
44
          client_secret: azureResource.azureClientSecret,
45
          grant_type: "client_credentials",
46
          resource: "499b84ac-1321-427f-aa17-267ca6975798/.default",
47
        }),
48
      }
49
    );
50
    const { access_token } = await response.json();
51
    repo_url = repo_url.replace(azureMatch[0], access_token);
52
  }
53
  const repo_name = basename(repo_url, ".git");
54
  await sh_run(4, "git", "clone", "--quiet", "--depth", "1", repo_url, repo_name);
55
  return repo_name;
56
}
57
async function git_push() {
58
  await sh_run(undefined, "git", "config", "user.email", process.env["WM_EMAIL"])
59
  await sh_run(undefined, "git", "config", "user.name", process.env["WM_USERNAME"])
60
  await sh_run(undefined, "git", "push");
61
}
62

63
async function sh_run(
64
  secret_position: number | undefined,
65
  cmd: string,
66
  ...args: string[]
67
) {
68
  const nargs = secret_position != undefined ? args.slice() : args;
69
  if (secret_position && secret_position < 0) {
70
    secret_position = nargs.length - 1 + secret_position;
71
  }
72
  let secret: string | undefined = undefined;
73
  if (secret_position != undefined) {
74
    nargs[secret_position] = "***";
75
    secret = args[secret_position];
76
  }
77

78
  console.log(`Running '${cmd} ${nargs.join(" ")} ...'`);
79
  const command = exec(`${cmd} ${args.join(" ")}`)
80
  try {
81
    const { stdout, stderr } = await command
82
    if (stdout.length > 0) {
83
      console.log(stdout);
84
    }
85
    if (stderr.length > 0) {
86
      console.log(stderr);
87
    }
88
    console.log("Command successfully executed");
89

90
  } catch (error) {
91
    let errorString = error.toString();
92
    if (secret) {
93
      errorString = errorString.replace(secret, "***");
94
    }
95
    const err = `SH command '${cmd} ${nargs.join(
96
      " "
97
    )}' returned with error ${errorString}`;
98
    throw Error(err);
99
  }
100
}
101

102
async function get_gh_app_token() {
103
  const workspace = process.env["WM_WORKSPACE"];
104
  const jobToken = process.env["WM_TOKEN"];
105

106
  const baseUrl =
107
    process.env["BASE_INTERNAL_URL"] ??
108
    process.env["BASE_URL"] ??
109
    "http://localhost:8000";
110

111
  const url = `${baseUrl}/api/w/${workspace}/github_app/token`;
112

113
  const response = await fetch(url, {
114
    method: 'POST',
115
    headers: {
116
      'Content-Type': 'application/json',
117
      'Authorization': `Bearer ${jobToken}`,
118
    },
119
    body: JSON.stringify({
120
      job_token: jobToken,
121
    }),
122
  });
123

124
  if (!response.ok) {
125
    throw new Error(`Error: ${response.statusText}`);
126
  }
127

128
  const data = await response.json();
129

130
  return data.token;
131
}
132

133
function prependTokenToGitHubUrl(gitHubUrl: string, installationToken: string) {
134
  if (!gitHubUrl || !installationToken) {
135
    throw new Error("Both GitHub URL and Installation Token are required.");
136
  }
137

138
  try {
139
    const url = new URL(gitHubUrl);
140

141
    // GitHub repository URL should be in the format: https://github.com/owner/repo.git
142
    if (url.hostname !== "github.com") {
143
      throw new Error("Invalid GitHub URL. Must be in the format 'https://github.com/owner/repo.git'.");
144
    }
145

146
    // Convert URL to include the installation token
147
    return `https://x-access-token:${installationToken}@github.com${url.pathname}`;
148
  } catch (e) {
149
    const error = e as Error
150
    throw new Error(`Invalid URL: ${error.message}`)
151
  }
152
}
Other submissions