add build env and path ingress controls
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
CREATE TABLE "build_env_variables" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"component_id" UUID NOT NULL,
|
||||||
|
"key" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
"is_secret" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"created_at" TIMESTAMP(3) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "build_env_variables_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "build_env_variables_component_id_key_key" ON "build_env_variables"("component_id", "key");
|
||||||
|
CREATE INDEX "build_env_variables_component_id_idx" ON "build_env_variables"("component_id");
|
||||||
|
CREATE INDEX "build_env_variables_component_id_is_secret_idx" ON "build_env_variables"("component_id", "is_secret");
|
||||||
|
|
||||||
|
ALTER TABLE "build_env_variables"
|
||||||
|
ADD CONSTRAINT "build_env_variables_component_id_fkey"
|
||||||
|
FOREIGN KEY ("component_id") REFERENCES "app_components"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
COMMENT ON TABLE "build_env_variables" IS '구성요소 빌드 시 주입할 환경변수';
|
||||||
|
COMMENT ON COLUMN "build_env_variables"."component_id" IS '빌드 대상 구성요소 ID';
|
||||||
|
COMMENT ON COLUMN "build_env_variables"."key" IS '환경변수 키';
|
||||||
|
COMMENT ON COLUMN "build_env_variables"."value" IS '환경변수 값';
|
||||||
|
COMMENT ON COLUMN "build_env_variables"."is_secret" IS '비밀값 여부';
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE "runtime_ingress_rules"
|
||||||
|
ADD COLUMN "strip_prefix" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN "runtime_ingress_rules"."strip_prefix" IS 'Traefik StripPrefix 미들웨어 적용 여부';
|
||||||
@@ -170,6 +170,7 @@ model AppComponent {
|
|||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
||||||
app App @relation(fields: [appId], references: [id], onDelete: Cascade)
|
app App @relation(fields: [appId], references: [id], onDelete: Cascade)
|
||||||
buildJobs BuildJob[]
|
buildJobs BuildJob[]
|
||||||
|
buildVariables BuildEnvVariable[]
|
||||||
runtimeVariables RuntimeEnvVariable[]
|
runtimeVariables RuntimeEnvVariable[]
|
||||||
ingressRules RuntimeIngressRule[]
|
ingressRules RuntimeIngressRule[]
|
||||||
|
|
||||||
@@ -177,6 +178,22 @@ model AppComponent {
|
|||||||
@@map("app_components")
|
@@map("app_components")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model BuildEnvVariable {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
componentId String @map("component_id") @db.Uuid
|
||||||
|
key String
|
||||||
|
value String
|
||||||
|
isSecret Boolean @default(false) @map("is_secret")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
||||||
|
component AppComponent @relation(fields: [componentId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([componentId, key])
|
||||||
|
@@index([componentId])
|
||||||
|
@@index([componentId, isSecret])
|
||||||
|
@@map("build_env_variables")
|
||||||
|
}
|
||||||
|
|
||||||
model BuildJob {
|
model BuildJob {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
componentId String @map("component_id") @db.Uuid
|
componentId String @map("component_id") @db.Uuid
|
||||||
@@ -319,6 +336,7 @@ model RuntimeIngressRule {
|
|||||||
host String
|
host String
|
||||||
path String @default("/")
|
path String @default("/")
|
||||||
servicePort Int @map("service_port")
|
servicePort Int @map("service_port")
|
||||||
|
stripPrefix Boolean @default(false) @map("strip_prefix")
|
||||||
tlsEnabled Boolean @default(false) @map("tls_enabled")
|
tlsEnabled Boolean @default(false) @map("tls_enabled")
|
||||||
tlsSecretName String? @map("tls_secret_name")
|
tlsSecretName String? @map("tls_secret_name")
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||||
|
|
||||||
import { LinkComponentSourceDto } from '../gitea/dto';
|
import { LinkComponentSourceDto } from '../gitea/dto';
|
||||||
import { AppsService } from './apps.service';
|
import { AppsService } from './apps.service';
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
CreateRuntimeEnvVariableDto,
|
CreateRuntimeEnvVariableDto,
|
||||||
CreateRuntimeIngressRuleDto,
|
CreateRuntimeIngressRuleDto,
|
||||||
EnsureRuntimeNamespaceDto,
|
EnsureRuntimeNamespaceDto,
|
||||||
|
ReplaceBuildEnvVariablesDto,
|
||||||
} from './dto';
|
} from './dto';
|
||||||
|
|
||||||
@Controller('apps')
|
@Controller('apps')
|
||||||
@@ -88,6 +89,19 @@ export class AppsController {
|
|||||||
return this.appsService.linkComponentSource(componentId, body);
|
return this.appsService.linkComponentSource(componentId, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('components/:componentId/build-env')
|
||||||
|
listComponentBuildEnv(@Param('componentId') componentId: string) {
|
||||||
|
return this.appsService.listComponentBuildEnv(componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('components/:componentId/build-env')
|
||||||
|
replaceComponentBuildEnv(
|
||||||
|
@Param('componentId') componentId: string,
|
||||||
|
@Body() body: ReplaceBuildEnvVariablesDto,
|
||||||
|
) {
|
||||||
|
return this.appsService.replaceComponentBuildEnv(componentId, body);
|
||||||
|
}
|
||||||
|
|
||||||
@Get('releases')
|
@Get('releases')
|
||||||
listReleases(@Query('appKey') appKey?: string) {
|
listReleases(@Query('appKey') appKey?: string) {
|
||||||
return this.appsService.listReleases(appKey);
|
return this.appsService.listReleases(appKey);
|
||||||
|
|||||||
+118
-1
@@ -22,6 +22,7 @@ import {
|
|||||||
CreateRuntimeEnvVariableDto,
|
CreateRuntimeEnvVariableDto,
|
||||||
CreateRuntimeIngressRuleDto,
|
CreateRuntimeIngressRuleDto,
|
||||||
EnsureRuntimeNamespaceDto,
|
EnsureRuntimeNamespaceDto,
|
||||||
|
ReplaceBuildEnvVariablesDto,
|
||||||
} from './dto';
|
} from './dto';
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
@@ -43,6 +44,9 @@ export class AppsService {
|
|||||||
components: {
|
components: {
|
||||||
orderBy: { name: 'asc' },
|
orderBy: { name: 'asc' },
|
||||||
include: {
|
include: {
|
||||||
|
buildVariables: {
|
||||||
|
orderBy: { key: 'asc' },
|
||||||
|
},
|
||||||
buildJobs: {
|
buildJobs: {
|
||||||
where: { status: 'SUCCEEDED' },
|
where: { status: 'SUCCEEDED' },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
@@ -194,6 +198,56 @@ export class AppsService {
|
|||||||
return { component };
|
return { component };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listComponentBuildEnv(componentId: string) {
|
||||||
|
const component = await this.prisma.appComponent.findUnique({
|
||||||
|
where: { id: componentId },
|
||||||
|
include: {
|
||||||
|
buildVariables: {
|
||||||
|
orderBy: { key: 'asc' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!component) {
|
||||||
|
throw new NotFoundException('앱 구성요소를 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { variables: component.buildVariables };
|
||||||
|
}
|
||||||
|
|
||||||
|
async replaceComponentBuildEnv(
|
||||||
|
componentId: string,
|
||||||
|
body: ReplaceBuildEnvVariablesDto,
|
||||||
|
) {
|
||||||
|
const component = await this.prisma.appComponent.findUnique({
|
||||||
|
where: { id: componentId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!component) {
|
||||||
|
throw new NotFoundException('앱 구성요소를 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const variables = normalizeBuildEnvVariables(body.variables ?? []);
|
||||||
|
|
||||||
|
await this.prisma.$transaction([
|
||||||
|
this.prisma.buildEnvVariable.deleteMany({ where: { componentId } }),
|
||||||
|
...(variables.length
|
||||||
|
? [
|
||||||
|
this.prisma.buildEnvVariable.createMany({
|
||||||
|
data: variables.map((variable) => ({
|
||||||
|
componentId,
|
||||||
|
key: variable.key,
|
||||||
|
value: variable.value,
|
||||||
|
isSecret: variable.isSecret,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return this.listComponentBuildEnv(componentId);
|
||||||
|
}
|
||||||
|
|
||||||
listReleases(appKey?: string) {
|
listReleases(appKey?: string) {
|
||||||
return this.prisma.appRelease.findMany({
|
return this.prisma.appRelease.findMany({
|
||||||
where: appKey ? { app: { key: appKey } } : undefined,
|
where: appKey ? { app: { key: appKey } } : undefined,
|
||||||
@@ -415,7 +469,21 @@ export class AppsService {
|
|||||||
if (runtimeProfile?.ingressRules.length) {
|
if (runtimeProfile?.ingressRules.length) {
|
||||||
for (const rule of runtimeProfile.ingressRules) {
|
for (const rule of runtimeProfile.ingressRules) {
|
||||||
const serviceName = sanitizeK8sName(rule.component.name);
|
const serviceName = sanitizeK8sName(rule.component.name);
|
||||||
const ingressName = sanitizeK8sName(`${rule.component.name}-${rule.host}`);
|
const ingressName = sanitizeK8sName(`${rule.component.name}-${rule.host}-${rule.path}`);
|
||||||
|
const middlewareName = rule.stripPrefix ? sanitizeK8sName(`${ingressName}-strip`) : null;
|
||||||
|
|
||||||
|
if (middlewareName) {
|
||||||
|
logs.push(`[apply] middleware/${middlewareName}`);
|
||||||
|
appendOutput(
|
||||||
|
logs,
|
||||||
|
await this.applyK8sManifest(createTraefikStripPrefixMiddlewareManifest({
|
||||||
|
namespace,
|
||||||
|
name: middlewareName,
|
||||||
|
prefix: rule.path,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
logs.push(`[apply] ingress/${ingressName}`);
|
logs.push(`[apply] ingress/${ingressName}`);
|
||||||
appendOutput(
|
appendOutput(
|
||||||
logs,
|
logs,
|
||||||
@@ -426,6 +494,7 @@ export class AppsService {
|
|||||||
path: rule.path,
|
path: rule.path,
|
||||||
serviceName,
|
serviceName,
|
||||||
servicePort: rule.servicePort,
|
servicePort: rule.servicePort,
|
||||||
|
middlewareName,
|
||||||
tlsEnabled: rule.tlsEnabled,
|
tlsEnabled: rule.tlsEnabled,
|
||||||
tlsSecretName: rule.tlsSecretName,
|
tlsSecretName: rule.tlsSecretName,
|
||||||
})),
|
})),
|
||||||
@@ -875,6 +944,7 @@ export class AppsService {
|
|||||||
host,
|
host,
|
||||||
path: pathValue,
|
path: pathValue,
|
||||||
servicePort: body.servicePort,
|
servicePort: body.servicePort,
|
||||||
|
stripPrefix: Boolean(body.stripPrefix),
|
||||||
tlsEnabled: Boolean(body.tlsEnabled),
|
tlsEnabled: Boolean(body.tlsEnabled),
|
||||||
tlsSecretName: body.tlsSecretName?.trim() || null,
|
tlsSecretName: body.tlsSecretName?.trim() || null,
|
||||||
},
|
},
|
||||||
@@ -1367,6 +1437,24 @@ function normalizeOptionalString(value?: string | null) {
|
|||||||
return trimmed || undefined;
|
return trimmed || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeBuildEnvVariables(
|
||||||
|
variables: Array<{ key?: string; value?: string; isSecret?: boolean }>,
|
||||||
|
) {
|
||||||
|
const byKey = new Map<string, { key: string; value: string; isSecret: boolean }>();
|
||||||
|
|
||||||
|
for (const variable of variables) {
|
||||||
|
const key = variable.key?.trim();
|
||||||
|
if (!key) continue;
|
||||||
|
byKey.set(key, {
|
||||||
|
key,
|
||||||
|
value: variable.value ?? '',
|
||||||
|
isSecret: variable.isSecret ?? false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...byKey.values()].sort((left, right) => left.key.localeCompare(right.key));
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeImageSource(value: string | null | undefined, componentType?: string) {
|
function normalizeImageSource(value: string | null | undefined, componentType?: string) {
|
||||||
const normalized = value?.trim().toUpperCase();
|
const normalized = value?.trim().toUpperCase();
|
||||||
if (normalized === 'EXTERNAL' || normalized === 'DATABASE') return normalized;
|
if (normalized === 'EXTERNAL' || normalized === 'DATABASE') return normalized;
|
||||||
@@ -1952,9 +2040,16 @@ function createIngressManifest(input: {
|
|||||||
path: string;
|
path: string;
|
||||||
serviceName: string;
|
serviceName: string;
|
||||||
servicePort: number;
|
servicePort: number;
|
||||||
|
middlewareName?: string | null;
|
||||||
tlsEnabled: boolean;
|
tlsEnabled: boolean;
|
||||||
tlsSecretName?: string | null;
|
tlsSecretName?: string | null;
|
||||||
}) {
|
}) {
|
||||||
|
const annotations: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (input.middlewareName) {
|
||||||
|
annotations['traefik.ingress.kubernetes.io/router.middlewares'] = `${input.namespace}-${input.middlewareName}@kubernetescrd`;
|
||||||
|
}
|
||||||
|
|
||||||
const manifest: Record<string, unknown> = {
|
const manifest: Record<string, unknown> = {
|
||||||
apiVersion: 'networking.k8s.io/v1',
|
apiVersion: 'networking.k8s.io/v1',
|
||||||
kind: 'Ingress',
|
kind: 'Ingress',
|
||||||
@@ -1962,6 +2057,7 @@ function createIngressManifest(input: {
|
|||||||
name: input.name,
|
name: input.name,
|
||||||
namespace: input.namespace,
|
namespace: input.namespace,
|
||||||
labels: getManagedLabels(input.name, 'ingress'),
|
labels: getManagedLabels(input.name, 'ingress'),
|
||||||
|
...(Object.keys(annotations).length > 0 ? { annotations } : {}),
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -1998,6 +2094,27 @@ function createIngressManifest(input: {
|
|||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createTraefikStripPrefixMiddlewareManifest(input: {
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
|
prefix: string;
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
apiVersion: 'traefik.io/v1alpha1',
|
||||||
|
kind: 'Middleware',
|
||||||
|
metadata: {
|
||||||
|
name: input.name,
|
||||||
|
namespace: input.namespace,
|
||||||
|
labels: getManagedLabels(input.name, 'ingress-middleware'),
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
stripPrefix: {
|
||||||
|
prefixes: [input.prefix],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createPostgresSecretManifest(input: {
|
function createPostgresSecretManifest(input: {
|
||||||
namespace: string;
|
namespace: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
OnModuleInit,
|
OnModuleInit,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { App, AppComponent, BuildJob } from '@prisma/client';
|
import { App, AppComponent, BuildEnvVariable, BuildJob } from '@prisma/client';
|
||||||
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||||
|
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
@@ -20,6 +20,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
type BuildJobWithComponent = BuildJob & {
|
type BuildJobWithComponent = BuildJob & {
|
||||||
component: AppComponent & {
|
component: AppComponent & {
|
||||||
app: App;
|
app: App;
|
||||||
|
buildVariables: BuildEnvVariable[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,6 +94,7 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
|
|||||||
component: {
|
component: {
|
||||||
include: {
|
include: {
|
||||||
app: true,
|
app: true,
|
||||||
|
buildVariables: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -116,6 +118,7 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
|
|||||||
component: {
|
component: {
|
||||||
include: {
|
include: {
|
||||||
app: true,
|
app: true,
|
||||||
|
buildVariables: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -360,12 +363,18 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
|
|||||||
'BUILD_ARTIFACT_DIR',
|
'BUILD_ARTIFACT_DIR',
|
||||||
path.join(os.tmpdir(), 'studio-admin-artifacts'),
|
path.join(os.tmpdir(), 'studio-admin-artifacts'),
|
||||||
);
|
);
|
||||||
|
const buildEnv = resolveBuildEnv(job.component.buildVariables);
|
||||||
|
|
||||||
|
if (job.component.buildVariables.length > 0) {
|
||||||
|
await append(`[artifact] build env ${job.component.buildVariables.map((variable) => `${variable.key}=${variable.isSecret ? '********' : variable.value}`).join(' ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
await append(`[artifact] install ${installCommand}`);
|
await append(`[artifact] install ${installCommand}`);
|
||||||
await runShellCommand({
|
await runShellCommand({
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
command: installCommand,
|
command: installCommand,
|
||||||
cwd: workDir,
|
cwd: workDir,
|
||||||
|
env: buildEnv,
|
||||||
append,
|
append,
|
||||||
onProcess: (child) => this.activeProcesses.set(job.id, child),
|
onProcess: (child) => this.activeProcesses.set(job.id, child),
|
||||||
onProcessEnd: () => this.activeProcesses.delete(job.id),
|
onProcessEnd: () => this.activeProcesses.delete(job.id),
|
||||||
@@ -376,6 +385,7 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
|
|||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
command: buildCommand,
|
command: buildCommand,
|
||||||
cwd: workDir,
|
cwd: workDir,
|
||||||
|
env: buildEnv,
|
||||||
append,
|
append,
|
||||||
onProcess: (child) => this.activeProcesses.set(job.id, child),
|
onProcess: (child) => this.activeProcesses.set(job.id, child),
|
||||||
onProcessEnd: () => this.activeProcesses.delete(job.id),
|
onProcessEnd: () => this.activeProcesses.delete(job.id),
|
||||||
@@ -625,6 +635,7 @@ async function runShellCommand({
|
|||||||
jobId,
|
jobId,
|
||||||
command,
|
command,
|
||||||
cwd,
|
cwd,
|
||||||
|
env,
|
||||||
append,
|
append,
|
||||||
onProcess,
|
onProcess,
|
||||||
onProcessEnd,
|
onProcessEnd,
|
||||||
@@ -632,6 +643,7 @@ async function runShellCommand({
|
|||||||
jobId: string;
|
jobId: string;
|
||||||
command: string;
|
command: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
append: (line: string) => Promise<void>;
|
append: (line: string) => Promise<void>;
|
||||||
onProcess: (child: ChildProcess) => void;
|
onProcess: (child: ChildProcess) => void;
|
||||||
onProcessEnd: () => void;
|
onProcessEnd: () => void;
|
||||||
@@ -641,7 +653,7 @@ async function runShellCommand({
|
|||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const child = spawn(command, {
|
const child = spawn(command, {
|
||||||
cwd,
|
cwd,
|
||||||
env: process.env,
|
env: env ?? process.env,
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
detached: true,
|
detached: true,
|
||||||
shell: true,
|
shell: true,
|
||||||
@@ -682,6 +694,17 @@ async function runShellCommand({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveBuildEnv(variables: BuildEnvVariable[]): NodeJS.ProcessEnv {
|
||||||
|
const env: NodeJS.ProcessEnv = { ...process.env };
|
||||||
|
|
||||||
|
for (const variable of variables) {
|
||||||
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(variable.key)) continue;
|
||||||
|
env[variable.key] = variable.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
async function findNewestArtifact(dir: string) {
|
async function findNewestArtifact(dir: string) {
|
||||||
const candidates = await findArtifactCandidates(dir).catch(() => []);
|
const candidates = await findArtifactCandidates(dir).catch(() => []);
|
||||||
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ export class CreateRuntimeIngressRuleDto {
|
|||||||
@Min(1)
|
@Min(1)
|
||||||
servicePort!: number;
|
servicePort!: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
stripPrefix?: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
tlsEnabled?: boolean;
|
tlsEnabled?: boolean;
|
||||||
@@ -134,6 +138,23 @@ export class CreateBuildJobDto {
|
|||||||
commitSha?: string;
|
commitSha?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BuildEnvVariableInputDto {
|
||||||
|
@IsString()
|
||||||
|
key!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
value!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isSecret?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReplaceBuildEnvVariablesDto {
|
||||||
|
@IsArray()
|
||||||
|
variables!: BuildEnvVariableInputDto[];
|
||||||
|
}
|
||||||
|
|
||||||
export class CreateAppReleaseDto {
|
export class CreateAppReleaseDto {
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
appId!: string;
|
appId!: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user