|
1 | 1 | import { V1Pod } from "@kubernetes/client-node"; |
2 | | -import { spawn } from "child_process"; |
3 | 2 | import { randomBytes } from "node:crypto"; |
4 | | -import { parse as yamlParse } from "yaml"; |
5 | 3 | import type { App, Deployment, HelmConfig } from "../db/models.ts"; |
6 | 4 | import { svcK8s } from "./cluster/kubernetes.ts"; |
7 | 5 | import { shouldImpersonate } from "./cluster/rancher.ts"; |
8 | 6 | import { getNamespace } from "./cluster/resources.ts"; |
9 | 7 | import { wrapWithLogExporter } from "./cluster/resources/logs.ts"; |
10 | 8 | import { env } from "./env.ts"; |
11 | 9 |
|
12 | | -type Dependency = { |
| 10 | +type Chart = { |
13 | 11 | name: string; |
14 | 12 | version: string; |
15 | | - repository?: string; |
16 | | - condition?: string; |
17 | | - tags?: string[]; |
18 | | - "import-values"?: string; |
19 | | - alias?: string; |
| 13 | + description?: string; |
| 14 | + note?: string; |
| 15 | + values: Record<string, any>; |
20 | 16 | }; |
21 | 17 |
|
22 | | -type Chart = { |
23 | | - apiVersion: string; |
| 18 | +type ChartTagList = { |
24 | 19 | name: string; |
25 | | - version: string; |
26 | | - kubeVersion?: string; |
27 | | - description?: string; |
28 | | - type?: string; |
29 | | - keywords?: string[]; |
30 | | - home?: string; |
31 | | - sources?: string[]; |
32 | | - dependencies?: Dependency[]; |
33 | | - maintainers?: { name: string; email: string; url: string }[]; |
34 | | - icon?: string; |
35 | | - appVersion?: string; |
36 | | - deprecated?: boolean; |
37 | | - annotations?: Record<string, string>; |
| 20 | + tags: string[]; |
38 | 21 | }; |
39 | 22 |
|
40 | | -const runHelm = (args: string[]) => { |
41 | | - return new Promise((resolve, reject) => { |
42 | | - const p = spawn("helm", args, { stdio: ["ignore", "pipe", "pipe"] }); |
43 | | - let out = "", |
44 | | - err = ""; |
45 | | - p.stdout.on("data", (d) => (out += d)); |
46 | | - p.stderr.on("data", (d) => (err += d)); |
47 | | - p.on("close", (code) => |
48 | | - code === 0 ? resolve(out) : reject(new Error(err || `helm exit ${code}`)), |
49 | | - ); |
50 | | - }); |
| 23 | +export const getChartToken = async () => { |
| 24 | + return fetch( |
| 25 | + `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/v2/service/token?service=harbor-registry&scope=repository:${env.CHART_PROJECT_NAME}/charts:pull`, |
| 26 | + ) |
| 27 | + .then((res) => { |
| 28 | + if (!res.ok) { |
| 29 | + console.error(res); |
| 30 | + throw new Error(res.statusText); |
| 31 | + } |
| 32 | + return res; |
| 33 | + }) |
| 34 | + .then((res) => res.text()) |
| 35 | + .then((res) => JSON.parse(res)) |
| 36 | + .then((res) => { |
| 37 | + return res.token; |
| 38 | + }); |
51 | 39 | }; |
52 | 40 |
|
53 | | -export const getChart = async ( |
54 | | - url: string, |
55 | | - version?: string, |
| 41 | +const getChart = async ( |
| 42 | + repository: string, |
| 43 | + version: string, |
| 44 | + token: string, |
56 | 45 | ): Promise<Chart> => { |
57 | | - const args = ["show", "chart"]; |
58 | | - if (version) { |
59 | | - args.push("version", version); |
60 | | - } |
61 | | - args.push(url); |
| 46 | + return fetch( |
| 47 | + `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/v2/${repository}/manifests/${version}`, |
| 48 | + { |
| 49 | + headers: { |
| 50 | + Authorization: `Bearer ${token}`, |
| 51 | + Accept: "application/vnd.oci.image.manifest.v1+json", |
| 52 | + }, |
| 53 | + }, |
| 54 | + ) |
| 55 | + .then((res) => { |
| 56 | + if (!res.ok) { |
| 57 | + throw new Error(res.statusText); |
| 58 | + } |
| 59 | + return res; |
| 60 | + }) |
| 61 | + .then((res) => res.text()) |
| 62 | + .then((res) => JSON.parse(res)) |
| 63 | + .then((res) => { |
| 64 | + const annotations = res.annotations; |
| 65 | + if ("anvilops-values" in annotations) { |
| 66 | + return { |
| 67 | + name: annotations["org.opencontainers.image.title"], |
| 68 | + version: annotations["org.opencontainers.image.version"], |
| 69 | + description: annotations["org.opencontainers.image.description"], |
| 70 | + note: annotations["anvilops-note"], |
| 71 | + values: JSON.parse(annotations["anvilops-values"]), |
| 72 | + }; |
| 73 | + } else { |
| 74 | + return null; |
| 75 | + } |
| 76 | + }); |
| 77 | +}; |
| 78 | + |
| 79 | +export const getLatestChart = async ( |
| 80 | + repository: string, |
| 81 | + token: string, |
| 82 | +): Promise<Chart | null> => { |
| 83 | + const chartTagList = await fetch( |
| 84 | + `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/v2/${repository}/tags/list`, |
| 85 | + { |
| 86 | + headers: { |
| 87 | + Authorization: `Bearer ${token}`, |
| 88 | + }, |
| 89 | + }, |
| 90 | + ) |
| 91 | + .then((res) => { |
| 92 | + if (!res.ok) { |
| 93 | + throw new Error(res.statusText); |
| 94 | + } |
| 95 | + return res; |
| 96 | + }) |
| 97 | + .then((res) => res.json() as Promise<ChartTagList>); |
62 | 98 |
|
63 | | - const result = (await runHelm(args)) as string; |
64 | | - const chart = (await yamlParse(result)) as Chart; |
65 | | - return chart; |
| 99 | + return await getChart( |
| 100 | + chartTagList.name, |
| 101 | + chartTagList.tags[chartTagList.tags.length - 1], |
| 102 | + token, |
| 103 | + ); |
66 | 104 | }; |
67 | 105 |
|
68 | 106 | export const upgrade = async ( |
|
0 commit comments