gitops-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -0
- package/dist/commands/bootstrap.d.ts +2 -0
- package/dist/commands/bootstrap.js +721 -0
- package/dist/commands/bootstrap.js.map +1 -0
- package/dist/commands/sops.d.ts +1 -0
- package/dist/commands/sops.js +300 -0
- package/dist/commands/sops.js.map +1 -0
- package/dist/core/bootstrap-runner.d.ts +13 -0
- package/dist/core/bootstrap-runner.js +194 -0
- package/dist/core/bootstrap-runner.js.map +1 -0
- package/dist/core/dependencies.d.ts +3 -0
- package/dist/core/dependencies.js +134 -0
- package/dist/core/dependencies.js.map +1 -0
- package/dist/core/encryption.d.ts +25 -0
- package/dist/core/encryption.js +209 -0
- package/dist/core/encryption.js.map +1 -0
- package/dist/core/flux.d.ts +6 -0
- package/dist/core/flux.js +60 -0
- package/dist/core/flux.js.map +1 -0
- package/dist/core/gitlab.d.ts +10 -0
- package/dist/core/gitlab.js +65 -0
- package/dist/core/gitlab.js.map +1 -0
- package/dist/core/kubernetes.d.ts +10 -0
- package/dist/core/kubernetes.js +81 -0
- package/dist/core/kubernetes.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas.d.ts +50 -0
- package/dist/schemas.js +56 -0
- package/dist/schemas.js.map +1 -0
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.js +26 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/log.d.ts +31 -0
- package/dist/utils/log.js +96 -0
- package/dist/utils/log.js.map +1 -0
- package/dist/utils/platform.d.ts +7 -0
- package/dist/utils/platform.js +21 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/shell.d.ts +41 -0
- package/dist/utils/shell.js +86 -0
- package/dist/utils/shell.js.map +1 -0
- package/dist/utils/wizard.d.ts +16 -0
- package/dist/utils/wizard.js +117 -0
- package/dist/utils/wizard.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { commandExists, execAsync } from "../utils/shell.js";
|
|
2
|
+
import { isMacOS, getArch } from "../utils/platform.js";
|
|
3
|
+
import { log, withSpinner } from "../utils/log.js";
|
|
4
|
+
async function runInstall(cmd) {
|
|
5
|
+
await execAsync(cmd);
|
|
6
|
+
}
|
|
7
|
+
const registry = [
|
|
8
|
+
{
|
|
9
|
+
name: "kubectl",
|
|
10
|
+
check: () => commandExists("kubectl"),
|
|
11
|
+
installDarwin: () => runInstall("brew install kubectl"),
|
|
12
|
+
installLinux: async () => {
|
|
13
|
+
const arch = getArch();
|
|
14
|
+
const version = await execAsync("curl -sL https://dl.k8s.io/release/stable.txt");
|
|
15
|
+
await execAsync(`curl -sL "https://dl.k8s.io/release/${version}/bin/linux/${arch}/kubectl" -o /tmp/kubectl`);
|
|
16
|
+
await execAsync("sudo install -o root -g root -m 0755 /tmp/kubectl /usr/local/bin/kubectl");
|
|
17
|
+
await execAsync("rm -f /tmp/kubectl");
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "helm",
|
|
22
|
+
check: () => commandExists("helm"),
|
|
23
|
+
installDarwin: () => runInstall("brew install helm"),
|
|
24
|
+
installLinux: () => runInstall("curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash"),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "k9s",
|
|
28
|
+
check: () => commandExists("k9s"),
|
|
29
|
+
installDarwin: () => runInstall("brew install derailed/k9s/k9s"),
|
|
30
|
+
installLinux: async () => {
|
|
31
|
+
await execAsync("wget -qO /tmp/k9s_linux_amd64.deb https://github.com/derailed/k9s/releases/latest/download/k9s_linux_amd64.deb");
|
|
32
|
+
await execAsync("sudo DEBIAN_FRONTEND=noninteractive apt install -y /tmp/k9s_linux_amd64.deb");
|
|
33
|
+
await execAsync("rm /tmp/k9s_linux_amd64.deb");
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "flux-operator",
|
|
38
|
+
check: () => commandExists("flux-operator"),
|
|
39
|
+
installDarwin: () => runInstall("brew install controlplaneio-fluxcd/tap/flux-operator"),
|
|
40
|
+
installLinux: async () => {
|
|
41
|
+
const arch = getArch();
|
|
42
|
+
const release = await execAsync(`curl -sL https://api.github.com/repos/controlplaneio-fluxcd/flux-operator/releases/latest | grep '"tag_name"' | cut -d'"' -f4`);
|
|
43
|
+
const num = release.replace(/^v/, "");
|
|
44
|
+
await execAsync(`curl -Lo /tmp/flux-operator.tar.gz "https://github.com/controlplaneio-fluxcd/flux-operator/releases/download/${release}/flux-operator_${num}_linux_${arch}.tar.gz"`);
|
|
45
|
+
await execAsync("tar -xzf /tmp/flux-operator.tar.gz -C /tmp flux-operator");
|
|
46
|
+
await execAsync("sudo install -o root -g root -m 0755 /tmp/flux-operator /usr/local/bin/flux-operator");
|
|
47
|
+
await execAsync("rm -f /tmp/flux-operator.tar.gz /tmp/flux-operator");
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "k3d",
|
|
52
|
+
check: () => commandExists("k3d"),
|
|
53
|
+
installDarwin: () => runInstall("brew install k3d"),
|
|
54
|
+
installLinux: () => runInstall('curl -sL "https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh" | bash'),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "git",
|
|
58
|
+
check: () => commandExists("git"),
|
|
59
|
+
installDarwin: () => runInstall("brew install git"),
|
|
60
|
+
installLinux: () => runInstall("sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git > /dev/null"),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "jq",
|
|
64
|
+
check: () => commandExists("jq"),
|
|
65
|
+
installDarwin: () => runInstall("brew install jq"),
|
|
66
|
+
installLinux: () => runInstall("sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq jq > /dev/null"),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "glab",
|
|
70
|
+
check: () => commandExists("glab"),
|
|
71
|
+
installDarwin: () => runInstall("brew install glab"),
|
|
72
|
+
installLinux: () => runInstall("sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq glab > /dev/null"),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "sops",
|
|
76
|
+
check: () => commandExists("sops"),
|
|
77
|
+
installDarwin: () => runInstall("brew install sops"),
|
|
78
|
+
installLinux: async () => {
|
|
79
|
+
const arch = getArch();
|
|
80
|
+
const os = "linux";
|
|
81
|
+
const version = await execAsync(`curl -sL https://api.github.com/repos/getsops/sops/releases/latest | grep '"tag_name"' | cut -d'"' -f4`);
|
|
82
|
+
await execAsync(`curl -Lo /tmp/sops "https://github.com/getsops/sops/releases/download/${version}/sops-${version}.${os}.${arch}"`);
|
|
83
|
+
await execAsync("sudo install -m 0755 /tmp/sops /usr/local/bin/sops");
|
|
84
|
+
await execAsync("rm -f /tmp/sops");
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "age",
|
|
89
|
+
check: () => commandExists("age") || commandExists("age-keygen"),
|
|
90
|
+
installDarwin: () => runInstall("brew install age"),
|
|
91
|
+
installLinux: async () => {
|
|
92
|
+
const arch = getArch();
|
|
93
|
+
const version = await execAsync(`curl -sL https://api.github.com/repos/FiloSottile/age/releases/latest | grep '"tag_name"' | cut -d'"' -f4`);
|
|
94
|
+
await execAsync(`curl -Lo /tmp/age.tar.gz "https://github.com/FiloSottile/age/releases/download/${version}/age-${version}-linux-${arch}.tar.gz"`);
|
|
95
|
+
await execAsync("tar -xzf /tmp/age.tar.gz -C /tmp");
|
|
96
|
+
await execAsync("sudo install -m 0755 /tmp/age/age /usr/local/bin/age");
|
|
97
|
+
await execAsync("sudo install -m 0755 /tmp/age/age-keygen /usr/local/bin/age-keygen");
|
|
98
|
+
await execAsync("rm -rf /tmp/age.tar.gz /tmp/age");
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
function getDep(name) {
|
|
103
|
+
const dep = registry.find((d) => d.name === name);
|
|
104
|
+
if (!dep)
|
|
105
|
+
throw new Error(`Unknown dependency: ${name}`);
|
|
106
|
+
return dep;
|
|
107
|
+
}
|
|
108
|
+
export async function ensureDependency(name) {
|
|
109
|
+
const dep = getDep(name);
|
|
110
|
+
if (dep.check()) {
|
|
111
|
+
log.success(`${dep.name} ✓`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
log.detail(`${dep.name} not found, installing...`);
|
|
115
|
+
await withSpinner(`Installing ${dep.name}`, async () => {
|
|
116
|
+
if (isMacOS()) {
|
|
117
|
+
await dep.installDarwin();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
await dep.installLinux();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
export async function ensureAll(names) {
|
|
125
|
+
for (const name of names) {
|
|
126
|
+
await ensureDependency(name);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function checkPrerequisite(name) {
|
|
130
|
+
if (!commandExists(name)) {
|
|
131
|
+
throw new Error(`Required tool '${name}' is not installed.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=dependencies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/core/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AASnD,KAAK,UAAU,UAAU,CAAC,GAAW;IACnC,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,QAAQ,GAAiB;IAC7B;QACE,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC;QACrC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,sBAAsB,CAAC;QACvD,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,MAAM,SAAS,CAC7B,+CAA+C,CAChD,CAAC;YACF,MAAM,SAAS,CACb,uCAAuC,OAAO,cAAc,IAAI,2BAA2B,CAC5F,CAAC;YACF,MAAM,SAAS,CACb,0EAA0E,CAC3E,CAAC;YACF,MAAM,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACxC,CAAC;KACF;IACD;QACE,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC;QAClC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC;QACpD,YAAY,EAAE,GAAG,EAAE,CACjB,UAAU,CACR,uFAAuF,CACxF;KACJ;IACD;QACE,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;QACjC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,+BAA+B,CAAC;QAChE,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,SAAS,CACb,gHAAgH,CACjH,CAAC;YACF,MAAM,SAAS,CACb,6EAA6E,CAC9E,CAAC;YACF,MAAM,SAAS,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;KACF;IACD;QACE,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,eAAe,CAAC;QAC3C,aAAa,EAAE,GAAG,EAAE,CAClB,UAAU,CAAC,sDAAsD,CAAC;QACpE,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,MAAM,SAAS,CAC7B,+HAA+H,CAChI,CAAC;YACF,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACtC,MAAM,SAAS,CACb,gHAAgH,OAAO,kBAAkB,GAAG,UAAU,IAAI,UAAU,CACrK,CAAC;YACF,MAAM,SAAS,CAAC,0DAA0D,CAAC,CAAC;YAC5E,MAAM,SAAS,CACb,sFAAsF,CACvF,CAAC;YACF,MAAM,SAAS,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;KACF;IACD;QACE,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;QACjC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC;QACnD,YAAY,EAAE,GAAG,EAAE,CACjB,UAAU,CACR,gFAAgF,CACjF;KACJ;IACD;QACE,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;QACjC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC;QACnD,YAAY,EAAE,GAAG,EAAE,CACjB,UAAU,CACR,sIAAsI,CACvI;KACJ;IACD;QACE,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC;QAChC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAClD,YAAY,EAAE,GAAG,EAAE,CACjB,UAAU,CACR,qIAAqI,CACtI;KACJ;IACD;QACE,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC;QAClC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC;QACpD,YAAY,EAAE,GAAG,EAAE,CACjB,UAAU,CACR,uIAAuI,CACxI;KACJ;IACD;QACE,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC;QAClC,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC;QACpD,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;YACvB,MAAM,EAAE,GAAG,OAAO,CAAC;YACnB,MAAM,OAAO,GAAG,MAAM,SAAS,CAC7B,wGAAwG,CACzG,CAAC;YACF,MAAM,SAAS,CACb,yEAAyE,OAAO,SAAS,OAAO,IAAI,EAAE,IAAI,IAAI,GAAG,CAClH,CAAC;YACF,MAAM,SAAS,CAAC,oDAAoD,CAAC,CAAC;YACtE,MAAM,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACrC,CAAC;KACF;IACD;QACE,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,YAAY,CAAC;QAChE,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC;QACnD,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,MAAM,SAAS,CAC7B,2GAA2G,CAC5G,CAAC;YACF,MAAM,SAAS,CACb,kFAAkF,OAAO,QAAQ,OAAO,UAAU,IAAI,UAAU,CACjI,CAAC;YACF,MAAM,SAAS,CAAC,kCAAkC,CAAC,CAAC;YACpD,MAAM,SAAS,CACb,sDAAsD,CACvD,CAAC;YACF,MAAM,SAAS,CACb,oEAAoE,CACrE,CAAC;YACF,MAAM,SAAS,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;KACF;CACF,CAAC;AAEF,SAAS,MAAM,CAAC,IAAY;IAC1B,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACzD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY;IACjD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC;QAChB,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,2BAA2B,CAAC,CAAC;IACnD,MAAM,WAAW,CAAC,cAAc,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,IAAI,EAAE;QACrD,IAAI,OAAO,EAAE,EAAE,CAAC;YACd,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAe;IAC7C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,qBAAqB,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SopsConfig } from "../schemas.js";
|
|
2
|
+
export declare function findSecretFiles(searchDir: string): string[];
|
|
3
|
+
export declare function isEncrypted(file: string): boolean;
|
|
4
|
+
export declare function generateAgeKey(cfg: SopsConfig): string;
|
|
5
|
+
export declare function getAgePublicKey(cfg: SopsConfig): string;
|
|
6
|
+
export declare function ageKeyExists(cfg: SopsConfig): boolean;
|
|
7
|
+
export declare function backupAgeKey(cfg: SopsConfig): void;
|
|
8
|
+
export declare function createSopsConfig(publicKey: string, cfg: SopsConfig): void;
|
|
9
|
+
export declare function encryptFile(file: string, cfg: SopsConfig, repoRoot: string): Promise<boolean>;
|
|
10
|
+
export declare function decryptFile(file: string, cfg: SopsConfig, repoRoot: string): Promise<boolean>;
|
|
11
|
+
export declare function editFile(file: string, cfg: SopsConfig): void;
|
|
12
|
+
export declare function encryptAll(cfg: SopsConfig, repoRoot: string): Promise<{
|
|
13
|
+
encrypted: number;
|
|
14
|
+
skipped: number;
|
|
15
|
+
total: number;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function decryptAll(cfg: SopsConfig, repoRoot: string): Promise<string[]>;
|
|
18
|
+
export interface SecretFileStatus {
|
|
19
|
+
path: string;
|
|
20
|
+
relpath: string;
|
|
21
|
+
status: "encrypted" | "template" | "plaintext";
|
|
22
|
+
}
|
|
23
|
+
export declare function getSecretStatus(repoRoot: string): SecretFileStatus[];
|
|
24
|
+
export declare function substituteAndEncrypt(file: string, vars: Record<string, string>, cfg: SopsConfig, repoRoot: string): void;
|
|
25
|
+
export declare function updateFluxKustomization(repoRoot: string, secretName: string): void;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync, copyFileSync, chmodSync, } from "node:fs";
|
|
2
|
+
import { join, basename, relative } from "node:path";
|
|
3
|
+
import { exec, execSafe } from "../utils/shell.js";
|
|
4
|
+
import { log } from "../utils/log.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// File discovery
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
function* walkYaml(dir) {
|
|
9
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
10
|
+
const full = join(dir, entry.name);
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
if (entry.name === "node_modules" || entry.name === ".git")
|
|
13
|
+
continue;
|
|
14
|
+
yield* walkYaml(full);
|
|
15
|
+
}
|
|
16
|
+
else if (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) {
|
|
17
|
+
yield full;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function findSecretFiles(searchDir) {
|
|
22
|
+
const results = [];
|
|
23
|
+
for (const file of walkYaml(searchDir)) {
|
|
24
|
+
if (basename(file).startsWith("_"))
|
|
25
|
+
continue;
|
|
26
|
+
const content = readFileSync(file, "utf-8");
|
|
27
|
+
if (/^\s*kind:\s*Secret\s*$/m.test(content)) {
|
|
28
|
+
results.push(file);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return results.sort();
|
|
32
|
+
}
|
|
33
|
+
export function isEncrypted(file) {
|
|
34
|
+
const content = readFileSync(file, "utf-8");
|
|
35
|
+
return content.includes("sops:") && content.includes("encrypted_regex");
|
|
36
|
+
}
|
|
37
|
+
function hasEnvsubstVars(file) {
|
|
38
|
+
const content = readFileSync(file, "utf-8");
|
|
39
|
+
return /\$\{[A-Za-z_]+\}/.test(content);
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Age key management
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
export function generateAgeKey(cfg) {
|
|
45
|
+
mkdirSync(cfg.keyDir, { recursive: true, mode: 0o700 });
|
|
46
|
+
exec(`age-keygen -o "${cfg.keyFile}" 2>&1`);
|
|
47
|
+
chmodSync(cfg.keyFile, 0o600);
|
|
48
|
+
return getAgePublicKey(cfg);
|
|
49
|
+
}
|
|
50
|
+
export function getAgePublicKey(cfg) {
|
|
51
|
+
const content = readFileSync(cfg.keyFile, "utf-8");
|
|
52
|
+
const match = content.match(/public key:\s*(\S+)/);
|
|
53
|
+
if (!match)
|
|
54
|
+
throw new Error("Could not extract public key from age key file");
|
|
55
|
+
return match[1];
|
|
56
|
+
}
|
|
57
|
+
export function ageKeyExists(cfg) {
|
|
58
|
+
return existsSync(cfg.keyFile);
|
|
59
|
+
}
|
|
60
|
+
export function backupAgeKey(cfg) {
|
|
61
|
+
mkdirSync(cfg.backupDir, { recursive: true, mode: 0o700 });
|
|
62
|
+
const backupPath = join(cfg.backupDir, `age.agekey.${Date.now()}.bak`);
|
|
63
|
+
copyFileSync(cfg.keyFile, backupPath);
|
|
64
|
+
log.warn(`Old key backed up to ${cfg.backupDir}/`);
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// SOPS config
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
export function createSopsConfig(publicKey, cfg) {
|
|
70
|
+
const content = `creation_rules:
|
|
71
|
+
# Encrypt only data and stringData fields in Kubernetes Secrets
|
|
72
|
+
- path_regex: .*(secret|Secret).*\\.yaml$
|
|
73
|
+
encrypted_regex: ^(data|stringData)$
|
|
74
|
+
age: ${publicKey}
|
|
75
|
+
`;
|
|
76
|
+
writeFileSync(cfg.configFile, content);
|
|
77
|
+
log.success(`Created ${cfg.configFile}`);
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Encrypt / decrypt
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
export async function encryptFile(file, cfg, repoRoot) {
|
|
83
|
+
const relpath = relative(repoRoot, file);
|
|
84
|
+
if (isEncrypted(file)) {
|
|
85
|
+
log.warn(`${relpath} is already encrypted`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const cmd = `SOPS_AGE_KEY_FILE="${cfg.keyFile}" sops --encrypt --in-place --config "${cfg.configFile}" "${file}"`;
|
|
89
|
+
const { exitCode, stderr } = execSafe(cmd);
|
|
90
|
+
if (exitCode === 0) {
|
|
91
|
+
log.success(`Encrypted: ${relpath}`);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
log.error(`Failed to encrypt: ${relpath}`);
|
|
95
|
+
if (stderr)
|
|
96
|
+
log.error(stderr);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
export async function decryptFile(file, cfg, repoRoot) {
|
|
100
|
+
const relpath = relative(repoRoot, file);
|
|
101
|
+
if (!isEncrypted(file)) {
|
|
102
|
+
log.warn(`${relpath} is not SOPS-encrypted`);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const cmd = `SOPS_AGE_KEY_FILE="${cfg.keyFile}" sops --decrypt --in-place --config "${cfg.configFile}" "${file}"`;
|
|
106
|
+
const { exitCode, stderr } = execSafe(cmd);
|
|
107
|
+
if (exitCode === 0) {
|
|
108
|
+
log.success(`Decrypted: ${relpath}`);
|
|
109
|
+
log.warn("Remember to re-encrypt before committing!");
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
log.error(`Failed to decrypt: ${relpath}`);
|
|
113
|
+
if (stderr)
|
|
114
|
+
log.error(stderr);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
export function editFile(file, cfg) {
|
|
118
|
+
exec(`SOPS_AGE_KEY_FILE="${cfg.keyFile}" EDITOR="${process.env.EDITOR ?? "vim"}" sops --config "${cfg.configFile}" "${file}"`);
|
|
119
|
+
}
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Batch operations
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
export async function encryptAll(cfg, repoRoot) {
|
|
124
|
+
const files = findSecretFiles(repoRoot);
|
|
125
|
+
let encrypted = 0;
|
|
126
|
+
let skipped = 0;
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
const relpath = relative(repoRoot, file);
|
|
129
|
+
if (isEncrypted(file)) {
|
|
130
|
+
log.info(` [skip] ${relpath} (already encrypted)`);
|
|
131
|
+
skipped++;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (hasEnvsubstVars(file)) {
|
|
135
|
+
log.warn(` [skip] ${relpath} (contains envsubst variables)`);
|
|
136
|
+
skipped++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const ok = await encryptFile(file, cfg, repoRoot);
|
|
140
|
+
if (ok)
|
|
141
|
+
encrypted++;
|
|
142
|
+
else
|
|
143
|
+
skipped++;
|
|
144
|
+
}
|
|
145
|
+
return { encrypted, skipped, total: files.length };
|
|
146
|
+
}
|
|
147
|
+
export async function decryptAll(cfg, repoRoot) {
|
|
148
|
+
const files = findSecretFiles(repoRoot);
|
|
149
|
+
const decrypted = [];
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
if (isEncrypted(file)) {
|
|
152
|
+
const ok = await decryptFile(file, cfg, repoRoot);
|
|
153
|
+
if (ok)
|
|
154
|
+
decrypted.push(file);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return decrypted;
|
|
158
|
+
}
|
|
159
|
+
export function getSecretStatus(repoRoot) {
|
|
160
|
+
return findSecretFiles(repoRoot).map((file) => {
|
|
161
|
+
const relpath = relative(repoRoot, file);
|
|
162
|
+
let status;
|
|
163
|
+
if (isEncrypted(file)) {
|
|
164
|
+
status = "encrypted";
|
|
165
|
+
}
|
|
166
|
+
else if (hasEnvsubstVars(file)) {
|
|
167
|
+
status = "template";
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
status = "plaintext";
|
|
171
|
+
}
|
|
172
|
+
return { path: file, relpath, status };
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Template substitution for secrets
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
export function substituteAndEncrypt(file, vars, cfg, repoRoot) {
|
|
179
|
+
let content = readFileSync(file, "utf-8");
|
|
180
|
+
content = content
|
|
181
|
+
.split("\n")
|
|
182
|
+
.filter((line) => !line.includes("# This secret is created by the bootstrap script") &&
|
|
183
|
+
!line.includes("# This is just a template"))
|
|
184
|
+
.join("\n");
|
|
185
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
186
|
+
content = content.replaceAll(`\${${key}}`, value);
|
|
187
|
+
}
|
|
188
|
+
writeFileSync(file, content);
|
|
189
|
+
encryptFile(file, cfg, repoRoot);
|
|
190
|
+
}
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Flux Kustomization patching
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
export function updateFluxKustomization(repoRoot, secretName) {
|
|
195
|
+
const syncFile = `${repoRoot}/clusters/_default-template/cluster-sync.yaml`;
|
|
196
|
+
if (!existsSync(syncFile)) {
|
|
197
|
+
log.warn("No cluster-sync.yaml template found — skipping auto-patch.");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const content = readFileSync(syncFile, "utf-8");
|
|
201
|
+
if (content.includes("decryption:")) {
|
|
202
|
+
log.success("Decryption already configured in cluster-sync.yaml");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const patched = content.replace(/^(\s*prune:)/m, ` decryption:\n provider: sops\n secretRef:\n name: ${secretName}\n$1`);
|
|
206
|
+
writeFileSync(syncFile, patched);
|
|
207
|
+
log.success("Added decryption config to cluster-sync.yaml");
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=encryption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../src/core/encryption.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BootstrapConfig } from "../schemas.js";
|
|
2
|
+
export declare function installOperator(): Promise<void>;
|
|
3
|
+
export declare function installInstance(config: BootstrapConfig, repoRoot: string): Promise<void>;
|
|
4
|
+
export declare function waitForInstance(): Promise<void>;
|
|
5
|
+
export declare function reconcile(): Promise<void>;
|
|
6
|
+
export declare function getStatus(): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { exec, execAsync } from "../utils/shell.js";
|
|
3
|
+
import { log, withSpinner } from "../utils/log.js";
|
|
4
|
+
export async function installOperator() {
|
|
5
|
+
const cmd = [
|
|
6
|
+
"helm install flux-operator",
|
|
7
|
+
"oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator",
|
|
8
|
+
"--namespace flux-system",
|
|
9
|
+
"--create-namespace",
|
|
10
|
+
"--set web.enabled=false",
|
|
11
|
+
"--wait",
|
|
12
|
+
].join(" ");
|
|
13
|
+
log.detail(cmd);
|
|
14
|
+
await withSpinner("Installing Flux Operator via Helm", () => execAsync(cmd));
|
|
15
|
+
}
|
|
16
|
+
export async function installInstance(config, repoRoot) {
|
|
17
|
+
const templatePath = `${repoRoot}/flux-instance-values.yaml`;
|
|
18
|
+
const tmpPath = "/tmp/flux-instance-values.yaml";
|
|
19
|
+
let content = readFileSync(templatePath, "utf-8");
|
|
20
|
+
content = envsubst(content, {
|
|
21
|
+
FLUX_GITLAB_REPO_OWNER: config.repoOwner,
|
|
22
|
+
FLUX_GITLAB_REPO_NAME: config.repoName,
|
|
23
|
+
FLUX_GITLAB_REPO_BRANCH: config.repoBranch,
|
|
24
|
+
CLUSTER_NAME: config.clusterName,
|
|
25
|
+
});
|
|
26
|
+
writeFileSync(tmpPath, content);
|
|
27
|
+
const cmd = [
|
|
28
|
+
"helm install flux",
|
|
29
|
+
"oci://ghcr.io/controlplaneio-fluxcd/charts/flux-instance",
|
|
30
|
+
"--namespace flux-system",
|
|
31
|
+
`--values ${tmpPath}`,
|
|
32
|
+
"--wait",
|
|
33
|
+
].join(" ");
|
|
34
|
+
log.detail(cmd);
|
|
35
|
+
await withSpinner("Installing Flux Instance via Helm", () => execAsync(cmd));
|
|
36
|
+
unlinkSync(tmpPath);
|
|
37
|
+
}
|
|
38
|
+
export async function waitForInstance() {
|
|
39
|
+
await withSpinner("Waiting for FluxInstance to be ready", () => execAsync("kubectl -n flux-system wait fluxinstance/flux --for=condition=Ready --timeout=5m"));
|
|
40
|
+
}
|
|
41
|
+
export async function reconcile() {
|
|
42
|
+
await withSpinner("Reconciling Flux", async () => {
|
|
43
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
44
|
+
await execAsync(`kubectl -n flux-system annotate --overwrite fluxinstance/flux reconcile.fluxcd.io/requestedAt="${ts}"`);
|
|
45
|
+
log.detail("Waiting for FluxInstance condition=Ready...");
|
|
46
|
+
await execAsync("kubectl -n flux-system wait fluxinstance/flux --for=condition=Ready --timeout=5m");
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function getStatus() {
|
|
50
|
+
try {
|
|
51
|
+
return exec("flux-operator -n flux-system get instance flux");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return "(unable to retrieve status)";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function envsubst(content, vars) {
|
|
58
|
+
return Object.entries(vars).reduce((acc, [key, value]) => acc.replaceAll(`\${${key}}`, value), content);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=flux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flux.js","sourceRoot":"","sources":["../../src/core/flux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,GAAG,GAAG;QACV,4BAA4B;QAC5B,0DAA0D;QAC1D,yBAAyB;QACzB,oBAAoB;QACpB,yBAAyB;QACzB,QAAQ;KACT,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChB,MAAM,WAAW,CAAC,mCAAmC,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAuB,EACvB,QAAgB;IAEhB,MAAM,YAAY,GAAG,GAAG,QAAQ,4BAA4B,CAAC;IAC7D,MAAM,OAAO,GAAG,gCAAgC,CAAC;IAEjD,IAAI,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE;QAC1B,sBAAsB,EAAE,MAAM,CAAC,SAAS;QACxC,qBAAqB,EAAE,MAAM,CAAC,QAAQ;QACtC,uBAAuB,EAAE,MAAM,CAAC,UAAU;QAC1C,YAAY,EAAE,MAAM,CAAC,WAAW;KACjC,CAAC,CAAC;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEhC,MAAM,GAAG,GAAG;QACV,mBAAmB;QACnB,0DAA0D;QAC1D,yBAAyB;QACzB,YAAY,OAAO,EAAE;QACrB,QAAQ;KACT,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChB,MAAM,WAAW,CAAC,mCAAmC,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAE7E,UAAU,CAAC,OAAO,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,WAAW,CAAC,sCAAsC,EAAE,GAAG,EAAE,CAC7D,SAAS,CACP,kFAAkF,CACnF,CACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,WAAW,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACzC,MAAM,SAAS,CACb,kGAAkG,EAAE,GAAG,CACxG,CAAC;QACF,GAAG,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;QAC1D,MAAM,SAAS,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,6BAA6B,CAAC;IACvC,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CACf,OAAe,EACf,IAA4B;IAE5B,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAChC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC,EAC1D,OAAO,CACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function authenticate(pat: string, host: string): Promise<string>;
|
|
2
|
+
export declare function resolveNamespaceId(namespace: string, host: string): Promise<string>;
|
|
3
|
+
export interface ProjectInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
httpUrl: string;
|
|
6
|
+
pathWithNamespace: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function getProject(namespace: string, name: string, host: string): Promise<ProjectInfo | null>;
|
|
9
|
+
export declare function createProject(name: string, namespaceId: string, host: string): Promise<ProjectInfo>;
|
|
10
|
+
export declare function configureGitCredentials(pat: string, cwd: string): void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { exec, execAsync, execSafe } from "../utils/shell.js";
|
|
2
|
+
import { log } from "../utils/log.js";
|
|
3
|
+
export async function authenticate(pat, host) {
|
|
4
|
+
await execAsync(`echo "${pat}" | glab auth login --hostname "${host}" --stdin`);
|
|
5
|
+
const username = await execAsync(`glab api "user" --hostname "${host}" | jq -r '.username'`);
|
|
6
|
+
log.success(`Authenticated as: ${username}`);
|
|
7
|
+
return username;
|
|
8
|
+
}
|
|
9
|
+
export async function resolveNamespaceId(namespace, host) {
|
|
10
|
+
const result = execSafe(`glab api --hostname "${host}" "namespaces/${namespace}" 2>/dev/null | jq -r '.id'`);
|
|
11
|
+
if (result.exitCode !== 0 || !result.stdout || result.stdout === "null") {
|
|
12
|
+
throw new Error(`Namespace '${namespace}' not found. Check the group or username.`);
|
|
13
|
+
}
|
|
14
|
+
log.success(`Namespace ID: ${result.stdout}`);
|
|
15
|
+
return result.stdout;
|
|
16
|
+
}
|
|
17
|
+
export async function getProject(namespace, name, host) {
|
|
18
|
+
const encoded = `${namespace}/${name}`.replace(/\//g, "%2F");
|
|
19
|
+
const { stdout, exitCode } = execSafe(`glab api --hostname "${host}" "projects/${encoded}" 2>/dev/null`);
|
|
20
|
+
if (exitCode !== 0 || !stdout)
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
const data = JSON.parse(stdout);
|
|
24
|
+
if (!data.id)
|
|
25
|
+
return null;
|
|
26
|
+
return {
|
|
27
|
+
id: String(data.id),
|
|
28
|
+
httpUrl: data.http_url_to_repo,
|
|
29
|
+
pathWithNamespace: data.path_with_namespace,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function createProject(name, namespaceId, host) {
|
|
37
|
+
const result = await execAsync([
|
|
38
|
+
"glab api --method POST",
|
|
39
|
+
`--hostname "${host}"`,
|
|
40
|
+
'"projects"',
|
|
41
|
+
`-f "name=${name}"`,
|
|
42
|
+
`-f "path=${name}"`,
|
|
43
|
+
`-f "namespace_id=${namespaceId}"`,
|
|
44
|
+
'-f "visibility=private"',
|
|
45
|
+
'-f "initialize_with_readme=false"',
|
|
46
|
+
].join(" "));
|
|
47
|
+
const data = JSON.parse(result);
|
|
48
|
+
return {
|
|
49
|
+
id: String(data.id),
|
|
50
|
+
httpUrl: data.http_url_to_repo,
|
|
51
|
+
pathWithNamespace: data.path_with_namespace,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function configureGitCredentials(pat, cwd) {
|
|
55
|
+
const { exitCode, stdout } = execSafe("git remote get-url origin 2>/dev/null");
|
|
56
|
+
let host = "gitlab.com";
|
|
57
|
+
if (exitCode === 0 && stdout) {
|
|
58
|
+
const match = stdout.match(/https:\/\/([^/]+)\//);
|
|
59
|
+
if (match)
|
|
60
|
+
host = match[1];
|
|
61
|
+
}
|
|
62
|
+
exec(`git config --local --replace-all credential.helper '!f() { echo "username=oauth2"; echo "password=${pat}"; }; f'`, { cwd });
|
|
63
|
+
log.success(`git credentials configured for ${host}`);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=gitlab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab.js","sourceRoot":"","sources":["../../src/core/gitlab.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function isClusterReachable(): boolean;
|
|
2
|
+
export declare function createK3dCluster(clusterName: string): Promise<void>;
|
|
3
|
+
export declare function installK3s(): Promise<void>;
|
|
4
|
+
export declare function setupKubeconfig(clusterName: string): string;
|
|
5
|
+
export declare function waitForCluster(): Promise<void>;
|
|
6
|
+
export declare function createNamespace(name: string): Promise<void>;
|
|
7
|
+
export declare function createSecret(name: string, namespace: string, data: Record<string, string>): Promise<void>;
|
|
8
|
+
export declare function secretExists(name: string, namespace: string): boolean;
|
|
9
|
+
export declare function deleteSecret(name: string, namespace: string): Promise<void>;
|
|
10
|
+
export declare function createSecretFromFile(name: string, namespace: string, key: string, filePath: string): Promise<void>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { exec, execAsync, execSafe } from "../utils/shell.js";
|
|
3
|
+
import { isMacOS, isCI } from "../utils/platform.js";
|
|
4
|
+
import { log, withSpinner } from "../utils/log.js";
|
|
5
|
+
import { KUBERNETES_VERSION } from "../schemas.js";
|
|
6
|
+
export function isClusterReachable() {
|
|
7
|
+
const { exitCode } = execSafe("kubectl cluster-info 2>/dev/null");
|
|
8
|
+
return exitCode === 0;
|
|
9
|
+
}
|
|
10
|
+
export async function createK3dCluster(clusterName) {
|
|
11
|
+
const { stdout } = execSafe("k3d cluster list 2>/dev/null");
|
|
12
|
+
if (stdout.includes(clusterName)) {
|
|
13
|
+
log.success(`k3d cluster '${clusterName}' already exists`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const cmd = [
|
|
17
|
+
"k3d cluster create",
|
|
18
|
+
clusterName,
|
|
19
|
+
`--image "rancher/k3s:v${KUBERNETES_VERSION}-k3s1"`,
|
|
20
|
+
'--k3s-arg "--disable=traefik@server:0"',
|
|
21
|
+
'--port "80:80@loadbalancer"',
|
|
22
|
+
'--port "443:443@loadbalancer"',
|
|
23
|
+
"--wait",
|
|
24
|
+
].join(" ");
|
|
25
|
+
log.detail(cmd);
|
|
26
|
+
await withSpinner(`Creating k3d cluster '${clusterName}'`, () => execAsync(cmd));
|
|
27
|
+
}
|
|
28
|
+
export async function installK3s() {
|
|
29
|
+
const { exitCode } = execSafe("command -v k3s");
|
|
30
|
+
if (exitCode === 0) {
|
|
31
|
+
log.success("k3s already installed");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await withSpinner("Installing k3s", () => execAsync(`curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v${KUBERNETES_VERSION}+k3s1" INSTALL_K3S_EXEC="server --disable=traefik --write-kubeconfig-mode=644" sh -`));
|
|
35
|
+
}
|
|
36
|
+
export function setupKubeconfig(clusterName) {
|
|
37
|
+
mkdirSync(`${process.env.HOME}/.kube`, { recursive: true });
|
|
38
|
+
if (isMacOS() || isCI()) {
|
|
39
|
+
const kubeconfigPath = `${process.env.HOME}/.kube/k3d-${clusterName}`;
|
|
40
|
+
const kubeconfig = exec(`k3d kubeconfig get ${clusterName}`);
|
|
41
|
+
writeFileSync(kubeconfigPath, kubeconfig, { mode: 0o600 });
|
|
42
|
+
process.env.KUBECONFIG = kubeconfigPath;
|
|
43
|
+
return kubeconfigPath;
|
|
44
|
+
}
|
|
45
|
+
exec("sudo cp /etc/rancher/k3s/k3s.yaml $HOME/.kube/config");
|
|
46
|
+
exec('sudo chown "$(id -u):$(id -g)" $HOME/.kube/config');
|
|
47
|
+
exec("chmod 600 $HOME/.kube/config");
|
|
48
|
+
const kubeconfigPath = `${process.env.HOME}/.kube/config`;
|
|
49
|
+
process.env.KUBECONFIG = kubeconfigPath;
|
|
50
|
+
return kubeconfigPath;
|
|
51
|
+
}
|
|
52
|
+
export async function waitForCluster() {
|
|
53
|
+
await withSpinner("Waiting for cluster to be ready", async () => {
|
|
54
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
55
|
+
await execAsync("kubectl wait --for=condition=Ready node --all --timeout=120s");
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export async function createNamespace(name) {
|
|
59
|
+
await execAsync(`kubectl create namespace ${name} --dry-run=client -o yaml | kubectl apply -f -`);
|
|
60
|
+
}
|
|
61
|
+
export async function createSecret(name, namespace, data) {
|
|
62
|
+
const literals = Object.entries(data)
|
|
63
|
+
.map(([k, v]) => `--from-literal=${k}='${v}'`)
|
|
64
|
+
.join(" ");
|
|
65
|
+
const cmd = `kubectl create secret generic ${name} --namespace=${namespace} ${literals}`;
|
|
66
|
+
log.detail(`kubectl create secret generic ${name} --namespace=${namespace}`);
|
|
67
|
+
await execAsync(cmd);
|
|
68
|
+
}
|
|
69
|
+
export function secretExists(name, namespace) {
|
|
70
|
+
const { exitCode } = execSafe(`kubectl get secret ${name} -n ${namespace} 2>/dev/null`);
|
|
71
|
+
return exitCode === 0;
|
|
72
|
+
}
|
|
73
|
+
export async function deleteSecret(name, namespace) {
|
|
74
|
+
await execAsync(`kubectl delete secret ${name} -n ${namespace}`);
|
|
75
|
+
}
|
|
76
|
+
export async function createSecretFromFile(name, namespace, key, filePath) {
|
|
77
|
+
const cmd = `kubectl create secret generic ${name} --namespace=${namespace} --from-file=${key}="${filePath}"`;
|
|
78
|
+
log.detail(`kubectl create secret generic ${name} --namespace=${namespace} --from-file=${key}`);
|
|
79
|
+
await execAsync(cmd);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=kubernetes.js.map
|