2024-02-07 22:48:42 +00:00
|
|
|
import EventStore from "../domain/EventStore";
|
|
|
|
import { ApplicationUpdateStarted } from "../domain/events/ApplicationUpdateStarted";
|
|
|
|
|
2024-01-29 23:36:12 +00:00
|
|
|
type TODO_TypeDefinition = any;
|
|
|
|
|
|
|
|
class Application {
|
|
|
|
private constructor(private readonly data: any) {}
|
|
|
|
|
|
|
|
static createFromDefinition(data: any): Application {
|
|
|
|
return new Application(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
get name(): string {
|
|
|
|
return this.data.appName;
|
|
|
|
}
|
|
|
|
get lastDeployedAt(): Date {
|
|
|
|
return new Date(this.currentVersion.timeStamp);
|
|
|
|
}
|
|
|
|
get imageName(): string {
|
|
|
|
return this.currentVersion.deployedImageName;
|
|
|
|
}
|
|
|
|
get dockerImage(): undefined | { name: string; tag: string; hubUrl: string } {
|
|
|
|
const match = this.imageName.match(/^(.*):(.*)$/);
|
|
|
|
if (!match || match[1].includes("/captain/")) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const name = match[1];
|
|
|
|
return {
|
|
|
|
name: name,
|
|
|
|
tag: match[2],
|
|
|
|
hubUrl: name.includes("/")
|
|
|
|
? `https://hub.docker.com/r/${name}`
|
|
|
|
: `https://hub.docker.com/_/${name}`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
isOlderThan(days: number): boolean {
|
|
|
|
const daysInMs = days * 24 * 60 * 60 * 1000;
|
|
|
|
const now = Date.now();
|
|
|
|
return now - this.lastDeployedAt.getTime() > daysInMs;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString(): string {
|
|
|
|
return JSON.stringify(this.data, null, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
private get currentVersion(): TODO_TypeDefinition {
|
|
|
|
return this.data.versions.find((version: { version: number }) => {
|
|
|
|
return version.version === this.data.deployedVersion;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Caprover {
|
|
|
|
private authToken: string = "";
|
|
|
|
private readonly apiUrl: string;
|
2024-02-07 22:48:42 +00:00
|
|
|
private readonly eventStore: EventStore;
|
2024-01-29 23:36:12 +00:00
|
|
|
|
2024-02-07 22:48:42 +00:00
|
|
|
constructor(
|
|
|
|
eventStore: EventStore,
|
|
|
|
readonly domain?: string,
|
|
|
|
private readonly password?: string
|
|
|
|
) {
|
2024-01-29 23:36:12 +00:00
|
|
|
if (!domain || !password) {
|
|
|
|
throw new Error("Missing domain or password");
|
|
|
|
}
|
|
|
|
this.apiUrl = `https://${domain}/api/v2`;
|
2024-02-07 22:48:42 +00:00
|
|
|
this.eventStore = eventStore;
|
2024-01-29 23:36:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async getApps(): Promise<Application[]> {
|
2024-03-10 08:49:52 +00:00
|
|
|
const res = await this.fetch("/user/apps/appDefinitions");
|
2024-01-29 23:36:12 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
res.data?.appDefinitions?.map(Application.createFromDefinition) ?? []
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-03-10 08:49:52 +00:00
|
|
|
async updateApplication(appName: string, version: string): Promise<void> {
|
|
|
|
console.debug("Caprover: Updating application", appName, "to", version);
|
|
|
|
const response = await this.fetch(`/user/apps/appData/${appName}`, {
|
|
|
|
method: "POST",
|
|
|
|
body: JSON.stringify({
|
|
|
|
captainDefinitionContent: JSON.stringify({
|
|
|
|
schemaVersion: 2,
|
|
|
|
imageName: version,
|
|
|
|
}),
|
|
|
|
gitHash: "",
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
|
|
|
|
console.debug("update application response", response);
|
|
|
|
if (response.status !== 100) {
|
|
|
|
throw new Error(
|
|
|
|
`Failed to update application ${appName} to ${version}: ${response.description}.`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-02-07 22:48:42 +00:00
|
|
|
this.eventStore.append(
|
|
|
|
new ApplicationUpdateStarted(
|
|
|
|
{ id: appName, newVersion: version },
|
|
|
|
new Date()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-01-29 23:36:12 +00:00
|
|
|
private async authenticate() {
|
|
|
|
if (this.authToken) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
console.debug("Trying to authenticate at", this.apiUrl);
|
|
|
|
|
|
|
|
const authResponse = await fetch(`${this.apiUrl}/login`, {
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
"x-namespace": "captain",
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
password: this.password,
|
|
|
|
}),
|
|
|
|
}).then((res) => res.json());
|
|
|
|
|
|
|
|
this.authToken = authResponse?.data?.token;
|
|
|
|
if (!this.authToken) {
|
|
|
|
throw new Error(`Failed to authenticate at ${this.apiUrl}`);
|
|
|
|
}
|
|
|
|
console.debug("Authenticated successfully at", this.apiUrl, this.authToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
private async fetch(path: string, options?: RequestInit) {
|
|
|
|
await this.authenticate();
|
|
|
|
|
2024-03-10 08:49:52 +00:00
|
|
|
console.debug("-> Caprover Fetching", options?.method, path);
|
2024-03-10 09:49:36 +00:00
|
|
|
|
|
|
|
const headers = new Headers(options?.headers);
|
|
|
|
headers.set("Content-Type", "application/json");
|
|
|
|
headers.set("x-namespace", "captain");
|
|
|
|
headers.set("x-captain-auth", this.authToken);
|
|
|
|
|
2024-01-29 23:36:12 +00:00
|
|
|
return fetch(`${this.apiUrl}${path}`, {
|
|
|
|
...options,
|
2024-03-10 09:49:36 +00:00
|
|
|
headers: headers,
|
2024-03-10 08:49:52 +00:00
|
|
|
}).then((res) => {
|
|
|
|
console.debug(
|
|
|
|
"\t<- Caprover Response",
|
|
|
|
options?.method,
|
|
|
|
path,
|
|
|
|
res.status,
|
|
|
|
res.statusText
|
|
|
|
);
|
|
|
|
return res.json();
|
2024-01-29 23:36:12 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export { Application };
|
|
|
|
export default Caprover;
|