diff --git a/prisma/migrations/20220304141408_service_secrets/migration.sql b/prisma/migrations/20220304141408_service_secrets/migration.sql
new file mode 100644
index 000000000..baa0c3f54
--- /dev/null
+++ b/prisma/migrations/20220304141408_service_secrets/migration.sql
@@ -0,0 +1,13 @@
+-- CreateTable
+CREATE TABLE "ServiceSecret" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "name" TEXT NOT NULL,
+ "value" TEXT NOT NULL,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL,
+ "serviceId" TEXT NOT NULL,
+ CONSTRAINT "ServiceSecret_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "ServiceSecret_name_serviceId_key" ON "ServiceSecret"("name", "serviceId");
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 688e312b7..8c4fdab53 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -122,6 +122,18 @@ model Secret {
@@unique([name, applicationId, isPRMRSecret])
}
+model ServiceSecret {
+ id String @id @default(cuid())
+ name String
+ value String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ service Service @relation(fields: [serviceId], references: [id])
+ serviceId String
+
+ @@unique([name, serviceId])
+}
+
model BuildLog {
id String @id @default(cuid())
applicationId String?
@@ -252,6 +264,7 @@ model Service {
minio Minio?
vscodeserver Vscodeserver?
wordpress Wordpress?
+ serviceSecret ServiceSecret[]
}
model PlausibleAnalytics {
diff --git a/src/lib/database/checks.ts b/src/lib/database/checks.ts
index d825de701..534d81966 100644
--- a/src/lib/database/checks.ts
+++ b/src/lib/database/checks.ts
@@ -15,6 +15,9 @@ export async function isDockerNetworkExists({ network }) {
return await prisma.destinationDocker.findFirst({ where: { network } });
}
+export async function isServiceSecretExists({ id, name }) {
+ return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
+}
export async function isSecretExists({ id, name, isPRMRSecret }) {
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
}
diff --git a/src/lib/database/secrets.ts b/src/lib/database/secrets.ts
index 167d061f5..835eb4def 100644
--- a/src/lib/database/secrets.ts
+++ b/src/lib/database/secrets.ts
@@ -1,6 +1,19 @@
import { encrypt, decrypt } from '$lib/crypto';
import { prisma } from './common';
+export async function listServiceSecrets(serviceId: string) {
+ let secrets = await prisma.serviceSecret.findMany({
+ where: { serviceId },
+ orderBy: { createdAt: 'desc' }
+ });
+ secrets = secrets.map((secret) => {
+ secret.value = decrypt(secret.value);
+ return secret;
+ });
+
+ return secrets;
+}
+
export async function listSecrets(applicationId: string) {
let secrets = await prisma.secret.findMany({
where: { applicationId },
@@ -14,6 +27,12 @@ export async function listSecrets(applicationId: string) {
return secrets;
}
+export async function createServiceSecret({ id, name, value }) {
+ value = encrypt(value);
+ return await prisma.serviceSecret.create({
+ data: { name, value, service: { connect: { id } } }
+ });
+}
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value);
return await prisma.secret.create({
@@ -21,10 +40,24 @@ export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecre
});
}
+export async function updateServiceSecret({ id, name, value }) {
+ value = encrypt(value);
+ const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
+
+ if (found) {
+ return await prisma.serviceSecret.updateMany({
+ where: { serviceId: id, name },
+ data: { value }
+ });
+ } else {
+ return await prisma.serviceSecret.create({
+ data: { name, value, service: { connect: { id } } }
+ });
+ }
+}
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value);
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
- console.log(found);
if (found) {
return await prisma.secret.updateMany({
@@ -38,6 +71,10 @@ export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecre
}
}
+export async function removeServiceSecret({ id, name }) {
+ return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
+}
+
export async function removeSecret({ id, name }) {
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
}
diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts
index 56606109b..ea0be0a83 100644
--- a/src/lib/database/services.ts
+++ b/src/lib/database/services.ts
@@ -19,7 +19,8 @@ export async function getService({ id, teamId }) {
plausibleAnalytics: true,
minio: true,
vscodeserver: true,
- wordpress: true
+ wordpress: true,
+ serviceSecret: true
}
});
@@ -42,6 +43,12 @@ export async function getService({ id, teamId }) {
if (body.wordpress?.mysqlRootUserPassword)
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
+ if (body?.serviceSecret.length > 0) {
+ body.serviceSecret = body.serviceSecret.map((s) => {
+ s.value = decrypt(s.value);
+ return s;
+ });
+ }
return { ...body };
}
@@ -159,5 +166,7 @@ export async function removeService({ id }) {
await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
+ await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
+
await prisma.service.delete({ where: { id } });
}
diff --git a/src/routes/services/[id]/_Services/_Wordpress.svelte b/src/routes/services/[id]/_Services/_Wordpress.svelte
index aa61aeb55..883178a22 100644
--- a/src/routes/services/[id]/_Services/_Wordpress.svelte
+++ b/src/routes/services/[id]/_Services/_Wordpress.svelte
@@ -25,7 +25,7 @@
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);`
- : null}>{service.wordpress.extraConfig || 'N/A'}{service.wordpress.extraConfig}
diff --git a/src/routes/services/[id]/__layout.svelte b/src/routes/services/[id]/__layout.svelte
index a096c14cd..fffce0007 100644
--- a/src/routes/services/[id]/__layout.svelte
+++ b/src/routes/services/[id]/__layout.svelte
@@ -57,13 +57,13 @@
+
+
+
+ |
+
+
+ |
+
+
+ {#if isNewSecret}
+
+
+
+ {:else}
+
+
+
+
+
+
+
+
+ {/if}
+ |
diff --git a/src/routes/services/[id]/secrets/index.json.ts b/src/routes/services/[id]/secrets/index.json.ts
new file mode 100644
index 000000000..22c22ae29
--- /dev/null
+++ b/src/routes/services/[id]/secrets/index.json.ts
@@ -0,0 +1,70 @@
+import { getTeam, getUserDetails } from '$lib/common';
+import * as db from '$lib/database';
+import { ErrorHandler } from '$lib/database';
+import type { RequestHandler } from '@sveltejs/kit';
+
+export const get: RequestHandler = async (event) => {
+ const { teamId, status, body } = await getUserDetails(event);
+ if (status === 401) return { status, body };
+
+ const { id } = event.params;
+ try {
+ const secrets = await db.listServiceSecrets(id);
+ return {
+ status: 200,
+ body: {
+ secrets: secrets.sort((a, b) => {
+ return ('' + a.name).localeCompare(b.name);
+ })
+ }
+ };
+ } catch (error) {
+ return ErrorHandler(error);
+ }
+};
+
+export const post: RequestHandler = async (event) => {
+ const { teamId, status, body } = await getUserDetails(event);
+ if (status === 401) return { status, body };
+
+ const { id } = event.params;
+ const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
+ try {
+ if (isNew) {
+ const found = await db.isServiceSecretExists({ id, name });
+ if (found) {
+ throw {
+ error: `Secret ${name} already exists.`
+ };
+ } else {
+ await db.createServiceSecret({ id, name, value });
+ return {
+ status: 201
+ };
+ }
+ } else {
+ await db.updateServiceSecret({ id, name, value });
+ return {
+ status: 201
+ };
+ }
+ } catch (error) {
+ return ErrorHandler(error);
+ }
+};
+export const del: RequestHandler = async (event) => {
+ const { teamId, status, body } = await getUserDetails(event);
+ if (status === 401) return { status, body };
+
+ const { id } = event.params;
+ const { name } = await event.request.json();
+
+ try {
+ await db.removeServiceSecret({ id, name });
+ return {
+ status: 200
+ };
+ } catch (error) {
+ return ErrorHandler(error);
+ }
+};
diff --git a/src/routes/services/[id]/secrets/index.svelte b/src/routes/services/[id]/secrets/index.svelte
new file mode 100644
index 000000000..d850fd37f
--- /dev/null
+++ b/src/routes/services/[id]/secrets/index.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+ Name |
+ Value |
+ Action |
+
+
+
+ {#each secrets as secret}
+ {#key secret.id}
+
+
+
+ {/key}
+ {/each}
+
+
+
+
+
+
diff --git a/src/routes/services/[id]/vaultwarden/start.json.ts b/src/routes/services/[id]/vaultwarden/start.json.ts
index f42481200..703a71473 100644
--- a/src/routes/services/[id]/vaultwarden/start.json.ts
+++ b/src/routes/services/[id]/vaultwarden/start.json.ts
@@ -14,25 +14,31 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
- const { type, version, destinationDockerId, destinationDocker } = service;
+ const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
- const baseImage = getServiceImage(type);
+ const image = getServiceImage(type);
const config = {
- image: `${baseImage}:${version}`,
- volume: `${id}-vaultwarden-data:/data/`
+ image: `${image}:${version}`,
+ volume: `${id}-vaultwarden-data:/data/`,
+ environmentVariables: {}
};
-
+ if (serviceSecret.length > 0) {
+ serviceSecret.forEach((secret) => {
+ config.environmentVariables[secret.name] = secret.value;
+ });
+ }
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.image,
+ environment: config.environmentVariables,
networks: [network],
volumes: [config.volume],
restart: 'always',
diff --git a/src/routes/services/[id]/vscodeserver/start.json.ts b/src/routes/services/[id]/vscodeserver/start.json.ts
index 29bf3326a..be43cb2a7 100644
--- a/src/routes/services/[id]/vscodeserver/start.json.ts
+++ b/src/routes/services/[id]/vscodeserver/start.json.ts
@@ -3,7 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
-import { ErrorHandler } from '$lib/database';
+import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -19,6 +19,7 @@ export const post: RequestHandler = async (event) => {
version,
destinationDockerId,
destinationDocker,
+ serviceSecret,
vscodeserver: { password }
} = service;
@@ -26,13 +27,20 @@ export const post: RequestHandler = async (event) => {
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
+ const image = getServiceImage(type);
+
const config = {
- image: `codercom/code-server:${version}`,
+ image: `${image}:${version}`,
volume: `${id}-vscodeserver-data:/home/coder`,
environmentVariables: {
PASSWORD: password
}
};
+ if (serviceSecret.length > 0) {
+ serviceSecret.forEach((secret) => {
+ config.environmentVariables[secret.name] = secret.value;
+ });
+ }
const composeFile = {
version: '3.8',
services: {
diff --git a/src/routes/services/[id]/wordpress/start.json.ts b/src/routes/services/[id]/wordpress/start.json.ts
index 54080478e..d1685d8b1 100644
--- a/src/routes/services/[id]/wordpress/start.json.ts
+++ b/src/routes/services/[id]/wordpress/start.json.ts
@@ -3,7 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
-import { ErrorHandler } from '$lib/database';
+import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -19,6 +19,7 @@ export const post: RequestHandler = async (event) => {
version,
fqdn,
destinationDockerId,
+ serviceSecret,
destinationDocker,
wordpress: {
mysqlDatabase,
@@ -32,11 +33,12 @@ export const post: RequestHandler = async (event) => {
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
+ const image = getServiceImage(type);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const config = {
wordpress: {
- image: `wordpress:${version}`,
+ image: `${image}:${version}`,
volume: `${id}-wordpress-data:/var/www/html`,
environmentVariables: {
WORDPRESS_DB_HOST: `${id}-mysql`,
@@ -58,6 +60,11 @@ export const post: RequestHandler = async (event) => {
}
}
};
+ if (serviceSecret.length > 0) {
+ serviceSecret.forEach((secret) => {
+ config.wordpress.environmentVariables[secret.name] = secret.value;
+ });
+ }
const composeFile = {
version: '3.8',
services: {