Upload files to "src"

This commit is contained in:
alex 2025-12-18 23:57:37 +01:00
parent 3a46ac8667
commit c03d135ae3

347
src/index.ts Normal file
View File

@ -0,0 +1,347 @@
import { error, getInput, getMultilineInput, info } from "@actions/core";
enum Input {
CoolifyApiKey = "coolify-api-key",
CoolifyUrl = "coolify-url",
ProjectId = "project-id",
ServerId = "server-id",
Name = "name",
Domains = "domains",
EnvironmentId = "environment-id",
EnvironmentName = "environment-name",
DockerCompose = "docker-compose",
Env = "env",
ImageName = "image-name",
ImageTag = "image-tag",
Ports = "ports",
Override = "override",
}
type CoolifyFetch = (path: string, init?: RequestInit) => Promise<Response>;
type ParamResult<P> = P extends { list: true } ? string[] : string;
type OptionalIfNotRequiredOrFallback<T> = {
[K in keyof T as T[K] extends { required: true }
? K
: T[K] extends { fallback: infer F }
? F extends undefined
? never
: K
: never]-?: ParamResult<T[K]>;
} & {
[K in keyof T as T[K] extends { required: true }
? never
: T[K] extends { fallback: infer F }
? F extends undefined
? K
: never
: K]?: ParamResult<T[K]>;
};
function getParameters<
T extends {
[key: string]: {
name: string;
required?: boolean;
fallback?: string;
list?: boolean;
};
}
>(parameters: T) {
return Object.fromEntries(
Object.entries(parameters).map(([name, _parameter]) => {
const parameter = _parameter as {
name: string;
required?: boolean;
fallback?: string;
list?: boolean;
};
return [
name,
(parameter.list
? getMultilineInput(parameter.name, parameter)
: getInput(parameter.name, parameter)) ||
parameter.fallback ||
undefined,
];
})
) as OptionalIfNotRequiredOrFallback<T>;
}
async function getUuid(
coolifyFetch: CoolifyFetch,
parameters: {
projectId: string;
environmentName?: string;
environmentId?: string;
serverId: string;
name: string;
domains: string;
}
) {
checkEnvironment(parameters);
try {
const environment = await coolifyFetch(
`/projects/${parameters.projectId}/${
parameters.environmentId ?? parameters.environmentName
}`
).then((res) => res.json() as Promise<{ id: number }>);
const response = await coolifyFetch("/applications").then(
(res) =>
res.json() as Promise<
{
uuid: string;
name: string;
environment_id: number;
destination: {
server: { uuid: string };
};
fqdn: string;
}[]
>
);
const matches = response.filter((application) => {
if (application.name != parameters.name) {
return false;
}
if (application.environment_id != environment.id) {
return false;
}
if (application.destination.server.uuid != parameters.serverId) {
return false;
}
if (application.fqdn != parameters.domains) {
return false;
}
return true;
});
if (matches.length > 1) {
throw new Error("Multiple applications match the parameters");
}
return matches.length == 1 ? matches[0].uuid : null;
} catch (err) {
error(err as string | Error);
return null;
}
}
async function deploy(coolifyFetch: CoolifyFetch, uuid: string) {
return await coolifyFetch(`/deploy?uuid=${uuid}&force=false`);
}
const commonParameters = {
projectId: { name: Input.ProjectId, required: true },
serverId: { name: Input.ServerId, required: true },
name: { name: Input.Name, required: true },
domains: { name: Input.Domains, required: true },
environmentName: { name: Input.EnvironmentName },
environmentId: { name: Input.EnvironmentId },
} satisfies Parameters<typeof getParameters>[0];
function checkEnvironment(parameters: {
environmentName?: string;
environmentId?: string;
}) {
if (!parameters.environmentName && !parameters.environmentId) {
throw new Error(
"'environment-name' or 'environment-id' must be defined"
);
}
}
async function createEnv(
coolifyFetch: CoolifyFetch,
uuid: string,
envs: string[]
) {
for (const env of envs) {
const [key, value] = env.split(/=(.*)/s).map((x) => x.trim());
await coolifyFetch(`/applications/${uuid}/envs`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key,
value,
is_preview: false,
is_literal: true,
is_multiline: false,
is_shown_once: false,
}),
}).then((res) => res.json() as Promise<{ uuid: string }>);
}
}
async function createDockerCompose(coolifyFetch: CoolifyFetch) {
const parameters = getParameters({
...commonParameters,
dockerCompose: { name: Input.DockerCompose, required: true },
overrideString: { name: Input.Override, fallback: "{}" },
env: { name: Input.Env, list: true },
});
checkEnvironment(parameters);
const override = JSON.parse(parameters.overrideString);
const hasEnv = parameters.env && parameters.env.length > 0;
const { uuid } = await coolifyFetch("/applications/dockercompose", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
project_uuid: parameters.projectId,
server_uuid: parameters.serverId,
environment_name: parameters.environmentName,
environment_uuid: parameters.environmentId,
docker_compose_raw: parameters.dockerCompose,
name: parameters.name,
domains: parameters.domains,
instant_deploy: false,
...override,
}),
}).then((res) => res.json() as Promise<{ uuid: string }>);
if (parameters.env && parameters.env.length > 0) {
createEnv(coolifyFetch, uuid, parameters.env);
}
}
async function createDockerImage(coolifyFetch: CoolifyFetch) {
const parameters = getParameters({
...commonParameters,
imageName: { name: Input.ImageName, required: true },
imageTag: { name: Input.ImageTag, required: true },
ports: { name: Input.Ports, fallback: "80" },
overrideString: { name: Input.Override, fallback: "{}" },
env: { name: Input.Env, list: true },
});
checkEnvironment(parameters);
const override = JSON.parse(parameters.overrideString);
const { uuid } = await coolifyFetch("/applications/dockerimage", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
project_uuid: parameters.projectId,
server_uuid: parameters.serverId,
environment_name: parameters.environmentName,
environment_uuid: parameters.environmentId,
docker_registry_image_name: parameters.imageName,
docker_registry_image_tag: parameters.imageTag,
name: parameters.name,
domains: parameters.domains,
ports_exposes: parameters.ports,
instant_deploy: false,
...override,
}),
}).then((res) => res.json() as Promise<{ uuid: string }>);
if (parameters.env && parameters.env.length > 0) {
createEnv(coolifyFetch, uuid, parameters.env);
}
}
async function update(coolifyFetch: CoolifyFetch) {
const parameters = getParameters(commonParameters);
checkEnvironment(parameters);
const existingApplication = await getUuid(coolifyFetch, parameters);
if (!existingApplication) {
throw new Error("Application doesn't exist");
}
return await deploy(coolifyFetch, existingApplication);
}
async function deleteApplication(coolifyFetch: CoolifyFetch) {
const parameters = getParameters(commonParameters);
checkEnvironment(parameters);
const existingApplication = await getUuid(coolifyFetch, parameters);
if (!existingApplication) {
throw new Error("No application found matching inputs");
}
return await coolifyFetch(`/applications/${existingApplication}`, {
method: "DELETE",
});
}
const actions: Record<
string,
{ action: (coolifyFetch: CoolifyFetch) => Promise<unknown> }
> = {
"create-docker-compose": {
action: createDockerCompose,
},
"create-docker-image": {
action: createDockerImage,
},
update: { action: update },
delete: { action: deleteApplication },
};
export async function run() {
const actionName = getInput("action", { required: true });
const action = actions[actionName];
if (action == undefined) {
throw new Error(
`"${actionName} is not a valid action. Valid options are:\n\n\t${Object.keys(
actions
).join("\n\t")}`
);
}
const apiKey = getInput("coolify-api-key", { required: true });
const coolifyUrl = (
getInput("coolify-url") || "https://coolify.skytechab.se"
).replace(/\/+$/, "");
info(`Executing action '${actionName}' to url ${coolifyUrl}`);
await action.action((input: string, init?: RequestInit) =>
fetch(`${coolifyUrl}/api/v1/${input.replace(/^\//, "")}`, {
...init,
headers: {
Authorization: `Bearer ${apiKey}`,
...init?.headers,
},
}).then(async (res) => {
if (!res.ok) {
const response = (await res.json()) as { message: string };
throw new Error(response.message);
}
return res;
})
);
info("Action completed");
}
run();