Some checks failed
Build & Release / build (push) Has been cancelled
DO registry API is unreliable for OCI Helm chart tags. Since the CI pushes images and chart with the same version, use lunarfront-app image tags which are always indexed correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
124 lines
4.7 KiB
TypeScript
124 lines
4.7 KiB
TypeScript
import { execSync } from "child_process";
|
|
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
import { tmpdir } from "os";
|
|
import { join } from "path";
|
|
import { config } from "../lib/config";
|
|
|
|
export async function getLatestChartVersion(): Promise<string> {
|
|
// Query backend image tags instead of Helm chart tags — DO API is unreliable for OCI helm artifacts
|
|
const res = await fetch("https://api.digitalocean.com/v2/registry/lunarfront/repositories/lunarfront-app/tags?page=1&per_page=100", {
|
|
headers: { Authorization: `Bearer ${config.doToken}` },
|
|
});
|
|
const data = await res.json() as { tags: { tag: string }[] };
|
|
const versions = (data.tags ?? [])
|
|
.map(t => t.tag)
|
|
.filter(t => /^\d+\.\d+\.\d+$/.test(t))
|
|
.sort((a, b) => {
|
|
const [aMaj, aMin, aPat] = a.split(".").map(Number);
|
|
const [bMaj, bMin, bPat] = b.split(".").map(Number);
|
|
return bMaj - aMaj || bMin - aMin || bPat - aPat;
|
|
});
|
|
if (!versions.length) throw new Error("No chart versions found in DOCR");
|
|
return versions[0];
|
|
}
|
|
|
|
function withRepo<T>(fn: (dir: string, env: NodeJS.ProcessEnv) => T): T {
|
|
const keyPath = join(tmpdir(), `manager-ssh-key-${Date.now()}`);
|
|
const dir = join(tmpdir(), `lunarfront-charts-${Date.now()}`);
|
|
|
|
const keyContent = config.gitSshKey.endsWith("\n") ? config.gitSshKey : config.gitSshKey + "\n";
|
|
writeFileSync(keyPath, keyContent, { mode: 0o600 });
|
|
|
|
const env = {
|
|
...process.env,
|
|
GIT_SSH_COMMAND: `ssh -i ${keyPath} -o StrictHostKeyChecking=no`,
|
|
};
|
|
|
|
try {
|
|
execSync(`git clone ${config.gitRepoUrl} ${dir}`, { env, stdio: "pipe" });
|
|
execSync(`git -C ${dir} config user.email "manager@lunarfront.tech"`, { env });
|
|
execSync(`git -C ${dir} config user.name "lunarfront-manager"`, { env });
|
|
const result = fn(dir, env);
|
|
const unpushed = execSync(`git -C ${dir} log origin/main..HEAD --oneline`, { env }).toString().trim();
|
|
if (unpushed) execSync(`git -C ${dir} push origin main`, { env, stdio: "pipe" });
|
|
return result;
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
rmSync(keyPath, { force: true });
|
|
}
|
|
}
|
|
|
|
export async function addCustomerChart(slug: string, appVersion: string) {
|
|
const version = (appVersion === "*" || appVersion === "latest") ? await getLatestChartVersion() : appVersion;
|
|
withRepo((dir, env) => {
|
|
const manifest = buildArgoCDApp(slug, version);
|
|
writeFileSync(join(dir, "customers", `${slug}.yaml`), manifest);
|
|
execSync(`git -C ${dir} add customers/${slug}.yaml`, { env });
|
|
execSync(`git -C ${dir} commit -m "feat: provision customer ${slug}"`, { env });
|
|
});
|
|
}
|
|
|
|
export async function upgradeCustomerChart(slug: string, version: string) {
|
|
withRepo((dir, env) => {
|
|
const manifest = buildArgoCDApp(slug, version);
|
|
writeFileSync(join(dir, "customers", `${slug}.yaml`), manifest);
|
|
execSync(`git -C ${dir} add customers/${slug}.yaml`, { env });
|
|
const diff = execSync(`git -C ${dir} diff --cached --name-only`, { env }).toString().trim();
|
|
if (!diff) return;
|
|
execSync(`git -C ${dir} commit -m "chore: upgrade customer ${slug} to chart ${version}"`, { env });
|
|
});
|
|
}
|
|
|
|
export async function upgradeAllCustomerCharts(slugs: string[], version: string) {
|
|
withRepo((dir, env) => {
|
|
for (const slug of slugs) {
|
|
const manifest = buildArgoCDApp(slug, version);
|
|
writeFileSync(join(dir, "customers", `${slug}.yaml`), manifest);
|
|
execSync(`git -C ${dir} add customers/${slug}.yaml`, { env });
|
|
}
|
|
const diff = execSync(`git -C ${dir} diff --cached --name-only`, { env }).toString().trim();
|
|
if (!diff) return;
|
|
execSync(`git -C ${dir} commit -m "chore: upgrade all customers to chart ${version}"`, { env });
|
|
});
|
|
}
|
|
|
|
export function removeCustomerChart(slug: string) {
|
|
withRepo((dir, env) => {
|
|
const filePath = join(dir, "customers", `${slug}.yaml`);
|
|
const tracked = execSync(`git -C ${dir} ls-files customers/${slug}.yaml`, { env }).toString().trim();
|
|
if (!tracked) return;
|
|
rmSync(filePath, { force: true });
|
|
execSync(`git -C ${dir} add customers/${slug}.yaml`, { env });
|
|
execSync(`git -C ${dir} commit -m "chore: deprovision customer ${slug}"`, { env });
|
|
});
|
|
}
|
|
|
|
function buildArgoCDApp(slug: string, version: string): string {
|
|
const revision = version;
|
|
return `apiVersion: argoproj.io/v1alpha1
|
|
kind: Application
|
|
metadata:
|
|
name: customer-${slug}
|
|
namespace: argocd
|
|
spec:
|
|
project: default
|
|
source:
|
|
repoURL: registry.digitalocean.com/lunarfront
|
|
chart: lunarfront
|
|
targetRevision: "${revision}"
|
|
helm:
|
|
parameters:
|
|
- name: ingress.host
|
|
value: ${slug}.lunarfront.tech
|
|
destination:
|
|
server: https://kubernetes.default.svc
|
|
namespace: customer-${slug}
|
|
syncPolicy:
|
|
automated:
|
|
prune: true
|
|
selfHeal: true
|
|
syncOptions:
|
|
- CreateNamespace=true
|
|
`;
|
|
}
|