mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-10 21:49:19 -06:00
feat: add PgAdmin support and enhance config map management functions
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import appService from "@/server/services/app.service";
|
||||
import dbGateService from "@/server/services/db-tool-services/dbgate.service";
|
||||
import pgAdminService from "@/server/services/db-tool-services/pgadmin.service";
|
||||
import phpMyAdminService from "@/server/services/db-tool-services/phpmyadmin.service";
|
||||
import { getAuthUserSession, simpleAction } from "@/server/utils/action-wrapper.utils";
|
||||
import { AppTemplateUtils } from "@/server/utils/app-template.utils";
|
||||
@@ -9,6 +10,14 @@ import { DatabaseTemplateInfoModel } from "@/shared/model/database-template-info
|
||||
import { ServerActionResult, SuccessActionResult } from "@/shared/model/server-action-error-return.model";
|
||||
import { ServiceException } from "@/shared/model/service.exception.model";
|
||||
|
||||
export type DbToolIds = 'dbgate' | 'phpmyadmin' | 'pgadmin';
|
||||
|
||||
const dbToolClasses = new Map([
|
||||
['dbgate', dbGateService],
|
||||
['phpmyadmin', phpMyAdminService],
|
||||
['pgadmin', pgAdminService]
|
||||
])
|
||||
|
||||
export const getDatabaseCredentials = async (appId: string) =>
|
||||
simpleAction(async () => {
|
||||
await getAuthUserSession();
|
||||
@@ -17,58 +26,52 @@ export const getDatabaseCredentials = async (appId: string) =>
|
||||
return new SuccessActionResult(credentials);
|
||||
}) as Promise<ServerActionResult<unknown, DatabaseTemplateInfoModel>>;
|
||||
|
||||
export const getIsDbToolActive = async (appId: string, dbTool: 'dbgate' | 'phpmyadmin') =>
|
||||
export const getIsDbToolActive = async (appId: string, dbTool: DbToolIds) =>
|
||||
simpleAction(async () => {
|
||||
await getAuthUserSession();
|
||||
if (dbTool === 'dbgate') {
|
||||
const isActive = await dbGateService.isDbToolRunning(appId);
|
||||
return new SuccessActionResult(isActive);
|
||||
} else if (dbTool === 'phpmyadmin') {
|
||||
const isActive = await phpMyAdminService.isDbToolRunning(appId);
|
||||
return new SuccessActionResult(isActive);
|
||||
} else {
|
||||
if (!dbToolClasses.has(dbTool)) {
|
||||
throw new ServiceException('Unknown db tool');
|
||||
}
|
||||
const isActive = dbToolClasses.get(dbTool)!.isDbToolRunning(appId);
|
||||
return new SuccessActionResult(isActive);
|
||||
}) as Promise<ServerActionResult<unknown, boolean>>;
|
||||
|
||||
export const deployDbTool = async (appId: string, dbTool: 'dbgate' | 'phpmyadmin') =>
|
||||
export const deployDbTool = async (appId: string, dbTool: DbToolIds) =>
|
||||
simpleAction(async () => {
|
||||
await getAuthUserSession();
|
||||
if (dbTool === 'dbgate') {
|
||||
await dbGateService.deploy(appId);
|
||||
return new SuccessActionResult();
|
||||
} else if (dbTool === 'phpmyadmin') {
|
||||
await phpMyAdminService.deploy(appId);
|
||||
return new SuccessActionResult();
|
||||
} else {
|
||||
|
||||
const currentDbTool = dbToolClasses.get(dbTool);
|
||||
if (!currentDbTool) {
|
||||
throw new ServiceException('Unknown db tool');
|
||||
}
|
||||
await currentDbTool.deploy(appId);
|
||||
return new SuccessActionResult();
|
||||
|
||||
}) as Promise<ServerActionResult<unknown, void>>;
|
||||
|
||||
export const getLoginCredentialsForRunningDbTool = async (appId: string, dbTool: 'dbgate' | 'phpmyadmin') =>
|
||||
export const getLoginCredentialsForRunningDbTool = async (appId: string, dbTool: DbToolIds) =>
|
||||
simpleAction(async () => {
|
||||
await getAuthUserSession();
|
||||
if (dbTool === 'dbgate') {
|
||||
return new SuccessActionResult(await dbGateService.getLoginCredentialsForRunningDbGate(appId));
|
||||
} else if (dbTool === 'phpmyadmin') {
|
||||
return new SuccessActionResult(await phpMyAdminService.getLoginCredentialsForRunningDbGate(appId));
|
||||
} else {
|
||||
|
||||
const currentDbTool = dbToolClasses.get(dbTool);
|
||||
if (!currentDbTool) {
|
||||
throw new ServiceException('Unknown db tool');
|
||||
}
|
||||
return new SuccessActionResult(await currentDbTool.getLoginCredentialsForRunningDbGate(appId));
|
||||
|
||||
}) as Promise<ServerActionResult<unknown, { url: string; username: string, password: string }>>;
|
||||
|
||||
export const deleteDbToolDeploymentForAppIfExists = async (appId: string, dbTool: 'dbgate' | 'phpmyadmin') =>
|
||||
export const deleteDbToolDeploymentForAppIfExists = async (appId: string, dbTool: DbToolIds) =>
|
||||
simpleAction(async () => {
|
||||
await getAuthUserSession();
|
||||
if (dbTool === 'dbgate') {
|
||||
await dbGateService.deleteToolForAppIfExists(appId);
|
||||
return new SuccessActionResult();
|
||||
} else if (dbTool === 'phpmyadmin') {
|
||||
await phpMyAdminService.deleteToolForAppIfExists(appId);
|
||||
return new SuccessActionResult();
|
||||
} else {
|
||||
|
||||
const currentDbTool = dbToolClasses.get(dbTool);
|
||||
if (!currentDbTool) {
|
||||
throw new ServiceException('Unknown db tool');
|
||||
}
|
||||
await currentDbTool.deleteToolForAppIfExists(appId);
|
||||
return new SuccessActionResult();
|
||||
|
||||
}) as Promise<ServerActionResult<unknown, void>>;
|
||||
|
||||
export const downloadDbGateFilesForApp = async (appId: string) =>
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function DbGateDbTool({
|
||||
|
||||
return <>
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Switch id="canary-channel-mode" disabled={loading || isDbGateActive === undefined} checked={isDbGateActive} onCheckedChange={async (checked) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { AppExtendedModel } from "@/shared/model/app-extended.model";
|
||||
import DbGateDbTool from "./db-gate-db-tool";
|
||||
import PhpMyAdminDbTool from "./phpmyadmin-db-tool";
|
||||
import DbToolSwitch from "./phpmyadmin-db-tool";
|
||||
|
||||
export default function DbToolsCard({
|
||||
app
|
||||
@@ -17,7 +17,9 @@ export default function DbToolsCard({
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<DbGateDbTool app={app} />
|
||||
{['MYSQL', 'MARIADB'].includes(app.appType) && <PhpMyAdminDbTool app={app} />}
|
||||
{['MYSQL', 'MARIADB'].includes(app.appType) && <DbToolSwitch app={app} toolId="phpmyadmin"
|
||||
toolNameString="PHP My Admin" />}
|
||||
{app.appType === 'POSTGRES' && <DbToolSwitch app={app} toolId="pgadmin" toolNameString="pgAdmin" />}
|
||||
</CardContent>
|
||||
</Card >
|
||||
</>;
|
||||
|
||||
@@ -4,17 +4,20 @@ import { Button } from "@/components/ui/button";
|
||||
import { useConfirmDialog } from "@/frontend/states/zustand.states";
|
||||
import { Toast } from "@/frontend/utils/toast.utils";
|
||||
import { Actions } from "@/frontend/utils/nextjs-actions.utils";
|
||||
import { deleteDbToolDeploymentForAppIfExists, deployDbTool, getIsDbToolActive, getLoginCredentialsForRunningDbTool } from "./actions";
|
||||
import { DbToolIds, deleteDbToolDeploymentForAppIfExists, deployDbTool, getIsDbToolActive, getLoginCredentialsForRunningDbTool } from "./actions";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import FullLoadingSpinner from "@/components/ui/full-loading-spinnter";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Code } from "@/components/custom/code";
|
||||
import LoadingSpinner from "@/components/ui/loading-spinner";
|
||||
|
||||
export default function PhpMyAdminDbTool({
|
||||
app
|
||||
export default function DbToolSwitch({
|
||||
app,
|
||||
toolId,
|
||||
toolNameString
|
||||
}: {
|
||||
app: AppExtendedModel;
|
||||
toolId: DbToolIds;
|
||||
toolNameString: string;
|
||||
}) {
|
||||
|
||||
const { openConfirmDialog } = useConfirmDialog();
|
||||
@@ -22,19 +25,19 @@ export default function PhpMyAdminDbTool({
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const loadIdDbToolActive = async (appId: string) => {
|
||||
const response = await Actions.run(() => getIsDbToolActive(appId, 'phpmyadmin'));
|
||||
const response = await Actions.run(() => getIsDbToolActive(appId, toolId));
|
||||
setIsDbToolActive(response);
|
||||
}
|
||||
|
||||
const openDbTool = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const credentials = await Actions.run(() => getLoginCredentialsForRunningDbTool(app.id, 'phpmyadmin'));
|
||||
const credentials = await Actions.run(() => getLoginCredentialsForRunningDbTool(app.id, toolId));
|
||||
setLoading(false);
|
||||
await openConfirmDialog({
|
||||
title: "Open DB Tool",
|
||||
description: <>
|
||||
PHP My Admin is ready and can be opened in a new tab. <br />
|
||||
{toolNameString} is ready and can be opened in a new tab. <br />
|
||||
Use the following credentials to login:
|
||||
<div className="pt-3 grid grid-cols-1 gap-1">
|
||||
<Label>Username</Label>
|
||||
@@ -45,7 +48,7 @@ export default function PhpMyAdminDbTool({
|
||||
<div><Code>{credentials.password}</Code></div>
|
||||
</div>
|
||||
<div>
|
||||
<Button variant='outline' onClick={() => window.open(credentials.url, '_blank')}>Open PHP My Admin</Button>
|
||||
<Button variant='outline' onClick={() => window.open(credentials.url, '_blank')}>Open {toolNameString}</Button>
|
||||
</div>
|
||||
</>,
|
||||
okButton: '',
|
||||
@@ -65,25 +68,25 @@ export default function PhpMyAdminDbTool({
|
||||
|
||||
return <>
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Switch id="canary-channel-mode" disabled={loading || isDbToolActive === undefined} checked={isDbToolActive} onCheckedChange={async (checked) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
if (checked) {
|
||||
await Toast.fromAction(() => deployDbTool(app.id, 'phpmyadmin'), 'PHP My Admin is now activated', 'activating PHP My Admin...');
|
||||
await Toast.fromAction(() => deployDbTool(app.id, toolId), `${toolNameString} is now activated`, `activating ${toolNameString}...`);
|
||||
} else {
|
||||
await Toast.fromAction(() => deleteDbToolDeploymentForAppIfExists(app.id, 'phpmyadmin'), 'PHP My Admin has been deactivated', 'Deactivating PHP My Admin...');
|
||||
await Toast.fromAction(() => deleteDbToolDeploymentForAppIfExists(app.id, toolId), `${toolNameString} has been deactivated`, `Deactivating ${toolNameString}...`);
|
||||
}
|
||||
await loadIdDbToolActive(app.id);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}} />
|
||||
<Label htmlFor="airplane-mode">PHP My Admin</Label>
|
||||
<Label htmlFor="airplane-mode">{toolNameString}</Label>
|
||||
</div>
|
||||
{isDbToolActive && <>
|
||||
<Button variant='outline' onClick={() => openDbTool()}
|
||||
disabled={!isDbToolActive || loading}>Open PHP My Admin</Button>
|
||||
disabled={!isDbToolActive || loading}>Open {toolNameString}</Button>
|
||||
</>}
|
||||
{(loading || isDbToolActive === undefined) && <LoadingSpinner></LoadingSpinner>}
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,6 @@ class ConfigMapService {
|
||||
|
||||
async createOrUpdateConfigMapForApp(app: AppExtendedModel) {
|
||||
|
||||
const existingConfigMaps = await this.getConfigMapsForApp(app.projectId, app.id);
|
||||
|
||||
if (app.appFileMounts.length === 0) {
|
||||
return { fileVolumeMounts: [], fileVolumes: [] };
|
||||
}
|
||||
@@ -52,30 +50,49 @@ class ConfigMapService {
|
||||
},
|
||||
};
|
||||
|
||||
if (existingConfigMaps.some(cm => cm.metadata!.name === currentConfigMapName)) {
|
||||
await k3s.core.replaceNamespacedConfigMap(currentConfigMapName, app.projectId, configMapManifest);
|
||||
} else {
|
||||
await k3s.core.createNamespacedConfigMap(app.projectId, configMapManifest);
|
||||
}
|
||||
await this.createOrUpdateConfigMap(app.projectId, configMapManifest);
|
||||
const containerMountPath = fileMount.containerMountPath;
|
||||
|
||||
fileVolumeMounts.push({
|
||||
name: currentConfigMapName,
|
||||
mountPath: fileMount.containerMountPath,
|
||||
subPath: filePath,
|
||||
readOnly: true
|
||||
});
|
||||
const { fileVolumeMount, fileVolume } = this.createFileVolumeConfig(currentConfigMapName, containerMountPath, filePath);
|
||||
|
||||
fileVolumes.push({
|
||||
name: currentConfigMapName,
|
||||
configMap: {
|
||||
name: currentConfigMapName,
|
||||
}
|
||||
});
|
||||
fileVolumeMounts.push(fileVolumeMount);
|
||||
fileVolumes.push(fileVolume);
|
||||
}
|
||||
|
||||
return { fileVolumeMounts, fileVolumes };
|
||||
}
|
||||
|
||||
createFileVolumeConfig(currentConfigMapName: string, containerMountPath: string, fileName: string, readOnly = true) {
|
||||
const fileVolumeMount = {
|
||||
name: currentConfigMapName,
|
||||
mountPath: containerMountPath,
|
||||
subPath: fileName,
|
||||
readOnly
|
||||
} as k8s.V1VolumeMount;
|
||||
|
||||
const fileVolume = {
|
||||
name: currentConfigMapName,
|
||||
configMap: {
|
||||
name: currentConfigMapName,
|
||||
}
|
||||
} as k8s.V1Volume;
|
||||
return { fileVolumeMount, fileVolume };
|
||||
}
|
||||
|
||||
async getExistingConfigMap(namespace: string, configMapName: string) {
|
||||
const configMaps = await k3s.core.listNamespacedConfigMap(namespace);
|
||||
return configMaps.body.items.find(cm => cm.metadata?.name === configMapName);
|
||||
}
|
||||
|
||||
async createOrUpdateConfigMap(namespace: string, configMapManifest: k8s.V1ConfigMap) {
|
||||
const currentConfigMapName = configMapManifest.metadata!.name!;
|
||||
const existingConfigMaps = await this.getExistingConfigMap(namespace, currentConfigMapName);
|
||||
if (!!existingConfigMaps) {
|
||||
await k3s.core.replaceNamespacedConfigMap(currentConfigMapName, namespace, configMapManifest);
|
||||
} else {
|
||||
await k3s.core.createNamespacedConfigMap(namespace, configMapManifest);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteUnusedConfigMaps(app: AppExtendedModel) {
|
||||
const existingConfigMaps = await this.getConfigMapsForApp(app.projectId, app.id);
|
||||
@@ -85,6 +102,13 @@ class ConfigMapService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteConfigMapIfExists(namespace: string, configMapName: string) {
|
||||
const existingConfigMap = await this.getExistingConfigMap(namespace, configMapName);
|
||||
if (!!existingConfigMap) {
|
||||
await k3s.core.deleteNamespacedConfigMap(configMapName, namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const configMapService = new ConfigMapService();
|
||||
|
||||
@@ -64,7 +64,7 @@ export class BaseDbToolService {
|
||||
return { url: `https://${traefikHostname}`, username, password };
|
||||
}
|
||||
|
||||
async deployToolForDatabase(appId: string, appPort: number, deplyomentBuilder: (app: AppExtendedModel) => V1Deployment) {
|
||||
async deployToolForDatabase(appId: string, appPort: number, deplyomentBuilder: (app: AppExtendedModel) => V1Deployment | Promise<V1Deployment>) {
|
||||
const app = await appService.getExtendedById(appId);
|
||||
const toolAppName = this.appIdToToolNameConverter(appId);
|
||||
|
||||
@@ -97,8 +97,8 @@ export class BaseDbToolService {
|
||||
}
|
||||
|
||||
|
||||
private async createOrUpdateDbGateDeployment(app: AppExtendedModel, deplyomentBuilder: (app: AppExtendedModel) => V1Deployment) {
|
||||
const body = deplyomentBuilder(app);
|
||||
private async createOrUpdateDbGateDeployment(app: AppExtendedModel, deplyomentBuilder: (app: AppExtendedModel) => V1Deployment | Promise<V1Deployment>) {
|
||||
const body = await deplyomentBuilder(app);
|
||||
const toolAppName = this.appIdToToolNameConverter(app.id);
|
||||
await deploymentService.applyDeployment(app.projectId, toolAppName, body);
|
||||
}
|
||||
|
||||
171
src/server/services/db-tool-services/pgadmin.service.ts
Normal file
171
src/server/services/db-tool-services/pgadmin.service.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { ServiceException } from "@/shared/model/service.exception.model";
|
||||
import { KubeObjectNameUtils } from "../../utils/kube-object-name.utils";
|
||||
import { randomBytes } from "crypto";
|
||||
import { V1Deployment, V1EnvVar } from "@kubernetes/client-node";
|
||||
import { Constants } from "@/shared/utils/constants";
|
||||
import { AppTemplateUtils } from "../../utils/app-template.utils";
|
||||
import appService from "../app.service";
|
||||
import { BaseDbToolService } from "./base-db-tool.service";
|
||||
import configMapService from "../config-map.service";
|
||||
import { AppExtendedModel } from "@/shared/model/app-extended.model";
|
||||
|
||||
class PgAdminService extends BaseDbToolService {
|
||||
|
||||
readonly pgPassPath = '/pgadmin-config/pgpass';
|
||||
readonly pgAdminConfigPath = '/pgadmin-config/servers.json';
|
||||
|
||||
constructor() {
|
||||
super((app) => KubeObjectNameUtils.toPgAdminId(app));
|
||||
}
|
||||
|
||||
async getLoginCredentialsForRunningDbGate(appId: string) {
|
||||
return await this.getLoginCredentialsForRunningTool(appId, (deployment) => {
|
||||
const username = deployment.spec?.template.spec?.containers[0].env?.find(e => e.name === 'PGADMIN_DEFAULT_EMAIL')?.value;
|
||||
const password = deployment.spec?.template.spec?.containers[0].env?.find(e => e.name === 'PGADMIN_DEFAULT_PASSWORD')?.value;
|
||||
if (!username || !password) {
|
||||
throw new ServiceException('Could not find login credentials for PGAdmin, please restart PGAdmin');
|
||||
}
|
||||
return { username, password };
|
||||
});
|
||||
}
|
||||
|
||||
async deleteToolForAppIfExists(appId: string) {
|
||||
const app = await appService.getExtendedById(appId);
|
||||
await configMapService.deleteConfigMapIfExists(app.projectId, KubeObjectNameUtils.getConfigMapName(this.appIdToToolNameConverter(app.id)));
|
||||
await configMapService.deleteConfigMapIfExists(app.projectId, 'pgpass-' + this.appIdToToolNameConverter(app.id));
|
||||
await super.deleteToolForAppIfExists(appId);
|
||||
}
|
||||
|
||||
async deploy(appId: string) {
|
||||
await this.deployToolForDatabase(appId, 80, async (app) => {
|
||||
|
||||
const projectId = app.projectId;
|
||||
const appName = this.appIdToToolNameConverter(app.id);
|
||||
const configMapName = KubeObjectNameUtils.getConfigMapName(appName);
|
||||
|
||||
const volumeConfigServerJsonFile = await this.createServerJsonConfigMap(configMapName, app);
|
||||
const volumeConfigPgPassFile = await this.createPgPassConfigMap(appName, app);
|
||||
|
||||
const authPassword = randomBytes(15).toString('hex');
|
||||
|
||||
const body: V1Deployment = {
|
||||
metadata: {
|
||||
name: appName
|
||||
},
|
||||
spec: {
|
||||
replicas: 1,
|
||||
selector: {
|
||||
matchLabels: {
|
||||
app: appName
|
||||
}
|
||||
},
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: appName
|
||||
},
|
||||
annotations: {
|
||||
[Constants.QS_ANNOTATION_APP_ID]: app.id,
|
||||
[Constants.QS_ANNOTATION_PROJECT_ID]: projectId,
|
||||
deploymentTimestamp: new Date().getTime() + "",
|
||||
"kubernetes.io/change-cause": `Deployment ${new Date().toISOString()}`
|
||||
}
|
||||
},
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: appName,
|
||||
image: 'dpage/pgadmin4:latest',
|
||||
imagePullPolicy: 'Always',
|
||||
env: [
|
||||
{
|
||||
name: 'PGADMIN_DEFAULT_EMAIL',
|
||||
value: 'quickstack@quickstack.dev'
|
||||
},
|
||||
{
|
||||
name: 'PGADMIN_DEFAULT_PASSWORD',
|
||||
value: authPassword
|
||||
},
|
||||
{
|
||||
name: 'PGADMIN_SERVER_JSON_FILE',
|
||||
value: this.pgAdminConfigPath
|
||||
},
|
||||
{
|
||||
name: 'PGPASS_FILE',
|
||||
value: this.pgPassPath // todo has to be chmod 0600
|
||||
},
|
||||
],
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: '/misc/ping',
|
||||
port: 80
|
||||
},
|
||||
initialDelaySeconds: 30,
|
||||
periodSeconds: 15,
|
||||
failureThreshold: 5,
|
||||
},
|
||||
volumeMounts: [volumeConfigServerJsonFile.fileVolumeMount, volumeConfigPgPassFile.fileVolumeMount]
|
||||
}
|
||||
],
|
||||
volumes: [volumeConfigServerJsonFile.fileVolume, volumeConfigPgPassFile.fileVolume]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return body;
|
||||
});
|
||||
}
|
||||
|
||||
private async createServerJsonConfigMap(configMapName: string, app: AppExtendedModel) {
|
||||
const dbCredentials = AppTemplateUtils.getDatabaseModelFromApp(app);
|
||||
const configMapManifest = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'ConfigMap',
|
||||
metadata: {
|
||||
name: configMapName,
|
||||
namespace: app.projectId,
|
||||
},
|
||||
data: {
|
||||
'servers.json': JSON.stringify({
|
||||
"Servers": {
|
||||
"1": {
|
||||
"Name": app.name,
|
||||
"Group": "Servers",
|
||||
"Host": dbCredentials.hostname,
|
||||
"Port": dbCredentials.port,
|
||||
"MaintenanceDB": 'postgres',
|
||||
"Username": dbCredentials.username,
|
||||
"SSLMode": "prefer",
|
||||
"PasswordExecCommand": `echo '${dbCredentials.password}'`, // todo does not work?!
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
await configMapService.createOrUpdateConfigMap(app.projectId, configMapManifest);
|
||||
const volumeConfigServerJsonFile = configMapService.createFileVolumeConfig(configMapName, this.pgAdminConfigPath, 'servers.json');
|
||||
return volumeConfigServerJsonFile;
|
||||
}
|
||||
|
||||
private async createPgPassConfigMap(appName: string, app: AppExtendedModel) {
|
||||
const dbCredentials = AppTemplateUtils.getDatabaseModelFromApp(app);
|
||||
const pgPassConfigMapName = 'pgpass-' + appName;
|
||||
const configMapManifestPgPass = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'ConfigMap',
|
||||
metadata: {
|
||||
name: pgPassConfigMapName,
|
||||
namespace: app.projectId,
|
||||
},
|
||||
data: {
|
||||
'pgpass': `${dbCredentials.hostname}:${dbCredentials.port}:postgres:${dbCredentials.username}:${dbCredentials.password}`,
|
||||
},
|
||||
};
|
||||
await configMapService.createOrUpdateConfigMap(app.projectId, configMapManifestPgPass);
|
||||
const volumeConfigPgPassFile = configMapService.createFileVolumeConfig(pgPassConfigMapName, this.pgPassPath, 'pgpass');
|
||||
return volumeConfigPgPassFile;
|
||||
}
|
||||
}
|
||||
const pgAdminService = new PgAdminService();
|
||||
export default pgAdminService;
|
||||
@@ -15,7 +15,14 @@ class SetupPodService {
|
||||
while (tries < maxTries) {
|
||||
const pod = await this.getPodOrUndefined(projectId, podName);
|
||||
if (pod && ['Running', 'Failed', 'Succeeded'].includes(pod.status?.phase!)) {
|
||||
return true;
|
||||
// check if running and ready (when passing readiness probe)
|
||||
if (pod.status?.phase === 'Running') {
|
||||
if (pod.status?.containerStatuses?.[0].ready) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, interval));
|
||||
|
||||
@@ -76,4 +76,8 @@ export class KubeObjectNameUtils {
|
||||
static toPhpMyAdminId(appId: string): `phpma-${string}` {
|
||||
return `phpma-${appId}`;
|
||||
}
|
||||
|
||||
static toPgAdminId(appId: string): `pga-${string}` {
|
||||
return `pga-${appId}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user