mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-09-14 11:06:15 +05:00
feat: implement comprehensive environment variable resolution in preparation functions, enhancing flexibility and support for nested references across services and environments
This commit is contained in:
parent
4c5771b55b
commit
fb749cd862
335
apps/dokploy/__test__/env/environment.test.ts
vendored
Normal file
335
apps/dokploy/__test__/env/environment.test.ts
vendored
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
import { prepareEnvironmentVariables } from "@dokploy/server/index";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const projectEnv = `
|
||||||
|
ENVIRONMENT=staging
|
||||||
|
DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
|
||||||
|
PORT=3000
|
||||||
|
`;
|
||||||
|
|
||||||
|
const environmentEnv = `
|
||||||
|
NODE_ENV=development
|
||||||
|
API_URL=https://api.dev.example.com
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
DATABASE_NAME=dev_database
|
||||||
|
SECRET_KEY=env-secret-123
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe("prepareEnvironmentVariables (environment variables)", () => {
|
||||||
|
it("resolves environment variables correctly", () => {
|
||||||
|
const serviceWithEnvVars = `
|
||||||
|
NODE_ENV=\${{environment.NODE_ENV}}
|
||||||
|
API_URL=\${{environment.API_URL}}
|
||||||
|
SERVICE_PORT=4000
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithEnvVars,
|
||||||
|
"",
|
||||||
|
environmentEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"NODE_ENV=development",
|
||||||
|
"API_URL=https://api.dev.example.com",
|
||||||
|
"SERVICE_PORT=4000",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves both project and environment variables", () => {
|
||||||
|
const serviceWithBoth = `
|
||||||
|
ENVIRONMENT=\${{project.ENVIRONMENT}}
|
||||||
|
NODE_ENV=\${{environment.NODE_ENV}}
|
||||||
|
API_URL=\${{environment.API_URL}}
|
||||||
|
DATABASE_URL=\${{project.DATABASE_URL}}
|
||||||
|
SERVICE_PORT=4000
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithBoth,
|
||||||
|
projectEnv,
|
||||||
|
environmentEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"ENVIRONMENT=staging",
|
||||||
|
"NODE_ENV=development",
|
||||||
|
"API_URL=https://api.dev.example.com",
|
||||||
|
"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
|
||||||
|
"SERVICE_PORT=4000",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles undefined environment variables", () => {
|
||||||
|
const serviceWithUndefined = `
|
||||||
|
UNDEFINED_VAR=\${{environment.UNDEFINED_VAR}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
prepareEnvironmentVariables(serviceWithUndefined, "", environmentEnv),
|
||||||
|
).toThrow("Invalid environment variable: environment.UNDEFINED_VAR");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows service variables to override environment variables", () => {
|
||||||
|
const serviceOverrideEnv = `
|
||||||
|
NODE_ENV=production
|
||||||
|
API_URL=\${{environment.API_URL}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceOverrideEnv,
|
||||||
|
"",
|
||||||
|
environmentEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"NODE_ENV=production", // Overrides environment variable
|
||||||
|
"API_URL=https://api.dev.example.com",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves complex references with project, environment, and service variables", () => {
|
||||||
|
const complexServiceEnv = `
|
||||||
|
FULL_DATABASE_URL=\${{project.DATABASE_URL}}/\${{environment.DATABASE_NAME}}
|
||||||
|
API_ENDPOINT=\${{environment.API_URL}}/\${{project.ENVIRONMENT}}/api
|
||||||
|
SERVICE_NAME=my-service
|
||||||
|
COMPLEX_VAR=\${{SERVICE_NAME}}-\${{environment.NODE_ENV}}-\${{project.ENVIRONMENT}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
complexServiceEnv,
|
||||||
|
projectEnv,
|
||||||
|
environmentEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"FULL_DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db/dev_database",
|
||||||
|
"API_ENDPOINT=https://api.dev.example.com/staging/api",
|
||||||
|
"SERVICE_NAME=my-service",
|
||||||
|
"COMPLEX_VAR=my-service-development-staging",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles environment variables with special characters", () => {
|
||||||
|
const specialEnvVars = `
|
||||||
|
SPECIAL_URL=https://special.com
|
||||||
|
COMPLEX_KEY="key-with-@#$%^&*()"
|
||||||
|
JWT_SECRET="secret-with-spaces and symbols!@#"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const serviceWithSpecial = `
|
||||||
|
FULL_URL=\${{environment.SPECIAL_URL}}/path?key=\${{environment.COMPLEX_KEY}}
|
||||||
|
AUTH_SECRET=\${{environment.JWT_SECRET}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithSpecial,
|
||||||
|
"",
|
||||||
|
specialEnvVars,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"FULL_URL=https://special.com/path?key=key-with-@#$%^&*()",
|
||||||
|
"AUTH_SECRET=secret-with-spaces and symbols!@#",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maintains precedence: service > environment > project", () => {
|
||||||
|
const conflictingProjectEnv = `
|
||||||
|
NODE_ENV=production-project
|
||||||
|
API_URL=https://project.api.com
|
||||||
|
DATABASE_NAME=project_db
|
||||||
|
`;
|
||||||
|
|
||||||
|
const conflictingEnvironmentEnv = `
|
||||||
|
NODE_ENV=development-environment
|
||||||
|
API_URL=https://environment.api.com
|
||||||
|
DATABASE_NAME=env_db
|
||||||
|
`;
|
||||||
|
|
||||||
|
const serviceWithConflicts = `
|
||||||
|
NODE_ENV=service-override
|
||||||
|
PROJECT_ENV=\${{project.NODE_ENV}}
|
||||||
|
ENV_VAR=\${{environment.API_URL}}
|
||||||
|
DB_NAME=\${{environment.DATABASE_NAME}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithConflicts,
|
||||||
|
conflictingProjectEnv,
|
||||||
|
conflictingEnvironmentEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"NODE_ENV=service-override", // Service wins
|
||||||
|
"PROJECT_ENV=production-project", // Project reference
|
||||||
|
"ENV_VAR=https://environment.api.com", // Environment reference
|
||||||
|
"DB_NAME=env_db", // Environment reference
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty environment variables", () => {
|
||||||
|
const serviceWithEmpty = `
|
||||||
|
SERVICE_VAR=test
|
||||||
|
PROJECT_VAR=\${{project.ENVIRONMENT}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithEmpty,
|
||||||
|
projectEnv,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual(["SERVICE_VAR=test", "PROJECT_VAR=staging"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles mixed quotes and environment variables", () => {
|
||||||
|
const envWithQuotes = `
|
||||||
|
QUOTED_VAR="development"
|
||||||
|
SINGLE_QUOTED='https://api.dev.example.com'
|
||||||
|
MIXED_VAR="value with 'single' quotes"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const serviceWithQuotes = `
|
||||||
|
NODE_ENV=\${{environment.QUOTED_VAR}}
|
||||||
|
API_URL=\${{environment.SINGLE_QUOTED}}
|
||||||
|
COMPLEX="Prefix-\${{environment.MIXED_VAR}}-Suffix"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithQuotes,
|
||||||
|
"",
|
||||||
|
envWithQuotes,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"NODE_ENV=development",
|
||||||
|
"API_URL=https://api.dev.example.com",
|
||||||
|
"COMPLEX=Prefix-value with 'single' quotes-Suffix",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves multiple environment references in single value", () => {
|
||||||
|
const multiRefEnv = `
|
||||||
|
HOST=localhost
|
||||||
|
PORT=5432
|
||||||
|
USERNAME=postgres
|
||||||
|
PASSWORD=secret123
|
||||||
|
`;
|
||||||
|
|
||||||
|
const serviceWithMultiRefs = `
|
||||||
|
DATABASE_URL=postgresql://\${{environment.USERNAME}}:\${{environment.PASSWORD}}@\${{environment.HOST}}:\${{environment.PORT}}/mydb
|
||||||
|
CONNECTION_STRING=\${{environment.HOST}}:\${{environment.PORT}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithMultiRefs,
|
||||||
|
"",
|
||||||
|
multiRefEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"DATABASE_URL=postgresql://postgres:secret123@localhost:5432/mydb",
|
||||||
|
"CONNECTION_STRING=localhost:5432",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles nested references with environment and project variables", () => {
|
||||||
|
const nestedProjectEnv = `
|
||||||
|
BASE_DOMAIN=example.com
|
||||||
|
PROTOCOL=https
|
||||||
|
`;
|
||||||
|
|
||||||
|
const nestedEnvironmentEnv = `
|
||||||
|
SUBDOMAIN=api.dev
|
||||||
|
PATH_PREFIX=/v1
|
||||||
|
`;
|
||||||
|
|
||||||
|
const serviceWithNested = `
|
||||||
|
FULL_URL=\${{project.PROTOCOL}}://\${{environment.SUBDOMAIN}}.\${{project.BASE_DOMAIN}}\${{environment.PATH_PREFIX}}/endpoint
|
||||||
|
API_BASE=\${{project.PROTOCOL}}://\${{environment.SUBDOMAIN}}.\${{project.BASE_DOMAIN}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithNested,
|
||||||
|
nestedProjectEnv,
|
||||||
|
nestedEnvironmentEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"FULL_URL=https://api.dev.example.com/v1/endpoint",
|
||||||
|
"API_BASE=https://api.dev.example.com",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws error for malformed environment variable references", () => {
|
||||||
|
const serviceWithMalformed = `
|
||||||
|
MALFORMED1=\${{environment.}}
|
||||||
|
MALFORMED2=\${{environment}}
|
||||||
|
VALID=\${{environment.NODE_ENV}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Should throw error for empty variable name after environment.
|
||||||
|
expect(() =>
|
||||||
|
prepareEnvironmentVariables(serviceWithMalformed, "", environmentEnv),
|
||||||
|
).toThrow("Invalid environment variable: environment.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles environment variables with numeric values", () => {
|
||||||
|
const numericEnv = `
|
||||||
|
PORT=8080
|
||||||
|
TIMEOUT=30
|
||||||
|
RETRY_COUNT=3
|
||||||
|
PERCENTAGE=99.5
|
||||||
|
`;
|
||||||
|
|
||||||
|
const serviceWithNumeric = `
|
||||||
|
SERVER_PORT=\${{environment.PORT}}
|
||||||
|
REQUEST_TIMEOUT=\${{environment.TIMEOUT}}
|
||||||
|
MAX_RETRIES=\${{environment.RETRY_COUNT}}
|
||||||
|
SUCCESS_RATE=\${{environment.PERCENTAGE}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithNumeric,
|
||||||
|
"",
|
||||||
|
numericEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"SERVER_PORT=8080",
|
||||||
|
"REQUEST_TIMEOUT=30",
|
||||||
|
"MAX_RETRIES=3",
|
||||||
|
"SUCCESS_RATE=99.5",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles boolean-like environment variables", () => {
|
||||||
|
const booleanEnv = `
|
||||||
|
DEBUG=true
|
||||||
|
ENABLED=false
|
||||||
|
PRODUCTION=1
|
||||||
|
DEVELOPMENT=0
|
||||||
|
`;
|
||||||
|
|
||||||
|
const serviceWithBoolean = `
|
||||||
|
DEBUG_MODE=\${{environment.DEBUG}}
|
||||||
|
FEATURE_ENABLED=\${{environment.ENABLED}}
|
||||||
|
IS_PROD=\${{environment.PRODUCTION}}
|
||||||
|
IS_DEV=\${{environment.DEVELOPMENT}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(
|
||||||
|
serviceWithBoolean,
|
||||||
|
"",
|
||||||
|
booleanEnv,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"DEBUG_MODE=true",
|
||||||
|
"FEATURE_ENABLED=false",
|
||||||
|
"IS_PROD=1",
|
||||||
|
"IS_DEV=0",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { findEnvironmentById } from "@dokploy/server";
|
import type { findEnvironmentsByProjectId } from "@dokploy/server";
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
@ -33,10 +33,9 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
type Environment = Omit<
|
type Environment = Awaited<
|
||||||
Awaited<ReturnType<typeof findEnvironmentById>>,
|
ReturnType<typeof findEnvironmentsByProjectId>
|
||||||
"project"
|
>[number];
|
||||||
>;
|
|
||||||
interface AdvancedEnvironmentSelectorProps {
|
interface AdvancedEnvironmentSelectorProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
currentEnvironmentId?: string;
|
currentEnvironmentId?: string;
|
||||||
@ -53,13 +52,12 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
const [selectedEnvironment, setSelectedEnvironment] =
|
const [selectedEnvironment, setSelectedEnvironment] =
|
||||||
useState<Environment | null>(null);
|
useState<Environment | null>(null);
|
||||||
|
|
||||||
const { data: project } = api.project.one.useQuery(
|
const { data: environments } = api.environment.byProjectId.useQuery(
|
||||||
{ projectId },
|
{ projectId: projectId },
|
||||||
{
|
{
|
||||||
enabled: !!projectId,
|
enabled: !!projectId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const environments = project?.environments || [];
|
|
||||||
|
|
||||||
// Form states
|
// Form states
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
@ -144,7 +142,7 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
|
|
||||||
// Redirect to production if we deleted the current environment
|
// Redirect to production if we deleted the current environment
|
||||||
if (selectedEnvironment.environmentId === currentEnvironmentId) {
|
if (selectedEnvironment.environmentId === currentEnvironmentId) {
|
||||||
const productionEnv = environments.find(
|
const productionEnv = environments?.find(
|
||||||
(env) => env.name === "production",
|
(env) => env.name === "production",
|
||||||
);
|
);
|
||||||
if (productionEnv) {
|
if (productionEnv) {
|
||||||
@ -190,7 +188,7 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
setIsDeleteDialogOpen(true);
|
setIsDeleteDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentEnv = environments.find(
|
const currentEnv = environments?.find(
|
||||||
(env) => env.environmentId === currentEnvironmentId,
|
(env) => env.environmentId === currentEnvironmentId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -210,7 +208,7 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
<DropdownMenuLabel>Environments</DropdownMenuLabel>
|
<DropdownMenuLabel>Environments</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
{environments.map((environment) => (
|
{environments?.map((environment) => (
|
||||||
<div key={environment.environmentId} className="flex items-center">
|
<div key={environment.environmentId} className="flex items-center">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="flex-1 cursor-pointer"
|
className="flex-1 cursor-pointer"
|
||||||
@ -229,6 +227,18 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
{/* Action buttons for non-production environments */}
|
{/* Action buttons for non-production environments */}
|
||||||
|
<EnvironmentVariables environmentId={environment.environmentId}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-6 w-6 p-0"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Terminal className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</EnvironmentVariables>
|
||||||
{environment.name !== "production" && (
|
{environment.name !== "production" && (
|
||||||
<div className="flex items-center gap-1 px-2">
|
<div className="flex items-center gap-1 px-2">
|
||||||
<Button
|
<Button
|
||||||
@ -242,20 +252,7 @@ export const AdvancedEnvironmentSelector = ({
|
|||||||
>
|
>
|
||||||
<PencilIcon className="h-3 w-3" />
|
<PencilIcon className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<EnvironmentVariables
|
|
||||||
environmentId={environment.environmentId}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-6 w-6 p-0"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Terminal className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</EnvironmentVariables>
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -1460,6 +1460,10 @@ export async function getServerSideProps(
|
|||||||
environmentId: params.environmentId,
|
environmentId: params.environmentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await helpers.environment.byProjectId.fetch({
|
||||||
|
projectId: params.projectId,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
environments,
|
environments,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { asc, eq } from "drizzle-orm";
|
||||||
|
|
||||||
export type Environment = typeof environments.$inferSelect;
|
export type Environment = typeof environments.$inferSelect;
|
||||||
|
|
||||||
@ -56,7 +56,17 @@ export const findEnvironmentById = async (environmentId: string) => {
|
|||||||
export const findEnvironmentsByProjectId = async (projectId: string) => {
|
export const findEnvironmentsByProjectId = async (projectId: string) => {
|
||||||
const projectEnvironments = await db.query.environments.findMany({
|
const projectEnvironments = await db.query.environments.findMany({
|
||||||
where: eq(environments.projectId, projectId),
|
where: eq(environments.projectId, projectId),
|
||||||
orderBy: (environments, { asc }) => [asc(environments.createdAt)],
|
orderBy: asc(environments.createdAt),
|
||||||
|
with: {
|
||||||
|
applications: true,
|
||||||
|
mariadb: true,
|
||||||
|
mongo: true,
|
||||||
|
mysql: true,
|
||||||
|
postgres: true,
|
||||||
|
redis: true,
|
||||||
|
compose: true,
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return projectEnvironments;
|
return projectEnvironments;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -206,6 +206,7 @@ const createEnvFile = (compose: ComposeNested) => {
|
|||||||
const envFileContent = prepareEnvironmentVariables(
|
const envFileContent = prepareEnvironmentVariables(
|
||||||
envContent,
|
envContent,
|
||||||
compose.environment.project.env,
|
compose.environment.project.env,
|
||||||
|
compose.environment.env,
|
||||||
).join("\n");
|
).join("\n");
|
||||||
|
|
||||||
if (!existsSync(dirname(envFilePath))) {
|
if (!existsSync(dirname(envFilePath))) {
|
||||||
@ -236,6 +237,7 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
|
|||||||
const envFileContent = prepareEnvironmentVariables(
|
const envFileContent = prepareEnvironmentVariables(
|
||||||
envContent,
|
envContent,
|
||||||
compose.environment.project.env,
|
compose.environment.project.env,
|
||||||
|
compose.environment.env,
|
||||||
).join("\n");
|
).join("\n");
|
||||||
|
|
||||||
const encodedContent = encodeBase64(envFileContent);
|
const encodedContent = encodeBase64(envFileContent);
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export const buildCustomDocker = async (
|
|||||||
const args = prepareEnvironmentVariables(
|
const args = prepareEnvironmentVariables(
|
||||||
buildArgs,
|
buildArgs,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dockerContextPath = getDockerContextPath(application);
|
const dockerContextPath = getDockerContextPath(application);
|
||||||
@ -51,7 +52,12 @@ export const buildCustomDocker = async (
|
|||||||
as it could be publicly exposed.
|
as it could be publicly exposed.
|
||||||
*/
|
*/
|
||||||
if (!publishDirectory) {
|
if (!publishDirectory) {
|
||||||
createEnvFile(dockerFilePath, env, application.environment.project.env);
|
createEnvFile(
|
||||||
|
dockerFilePath,
|
||||||
|
env,
|
||||||
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
@ -93,6 +99,7 @@ export const getDockerCommand = (
|
|||||||
const args = prepareEnvironmentVariables(
|
const args = prepareEnvironmentVariables(
|
||||||
buildArgs,
|
buildArgs,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dockerContextPath =
|
const dockerContextPath =
|
||||||
@ -122,6 +129,7 @@ export const getDockerCommand = (
|
|||||||
dockerFilePath,
|
dockerFilePath,
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export const buildHeroku = async (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const args = [
|
const args = [
|
||||||
@ -54,6 +55,7 @@ export const getHerokuCommand = (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
|
|||||||
@ -149,6 +149,7 @@ export const mechanizeDockerContainer = async (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
const image = getImageName(application);
|
const image = getImageName(application);
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export const buildNixpacks = async (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
const writeToStream = (data: string) => {
|
const writeToStream = (data: string) => {
|
||||||
@ -102,6 +103,7 @@ export const getNixpacksCommand = (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
const args = ["build", buildAppDirectory, "--name", appName];
|
const args = ["build", buildAppDirectory, "--name", appName];
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export const buildPaketo = async (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const args = [
|
const args = [
|
||||||
@ -53,6 +54,7 @@ export const getPaketoCommand = (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export const buildRailpack = async (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -124,6 +125,7 @@ export const getRailpackCommand = (
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
env,
|
env,
|
||||||
application.environment.project.env,
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prepare command
|
// Prepare command
|
||||||
|
|||||||
@ -6,14 +6,17 @@ export const createEnvFile = (
|
|||||||
directory: string,
|
directory: string,
|
||||||
env: string | null,
|
env: string | null,
|
||||||
projectEnv?: string | null,
|
projectEnv?: string | null,
|
||||||
|
environmentEnv?: string | null,
|
||||||
) => {
|
) => {
|
||||||
const envFilePath = join(dirname(directory), ".env");
|
const envFilePath = join(dirname(directory), ".env");
|
||||||
if (!existsSync(dirname(envFilePath))) {
|
if (!existsSync(dirname(envFilePath))) {
|
||||||
mkdirSync(dirname(envFilePath), { recursive: true });
|
mkdirSync(dirname(envFilePath), { recursive: true });
|
||||||
}
|
}
|
||||||
const envFileContent = prepareEnvironmentVariables(env, projectEnv).join(
|
const envFileContent = prepareEnvironmentVariables(
|
||||||
"\n",
|
env,
|
||||||
);
|
projectEnv,
|
||||||
|
environmentEnv,
|
||||||
|
).join("\n");
|
||||||
writeFileSync(envFilePath, envFileContent);
|
writeFileSync(envFilePath, envFileContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,10 +24,13 @@ export const createEnvFileCommand = (
|
|||||||
directory: string,
|
directory: string,
|
||||||
env: string | null,
|
env: string | null,
|
||||||
projectEnv?: string | null,
|
projectEnv?: string | null,
|
||||||
|
environmentEnv?: string | null,
|
||||||
) => {
|
) => {
|
||||||
const envFileContent = prepareEnvironmentVariables(env, projectEnv).join(
|
const envFileContent = prepareEnvironmentVariables(
|
||||||
"\n",
|
env,
|
||||||
);
|
projectEnv,
|
||||||
|
environmentEnv,
|
||||||
|
).join("\n");
|
||||||
|
|
||||||
const encodedContent = encodeBase64(envFileContent || "");
|
const encodedContent = encodeBase64(envFileContent || "");
|
||||||
const envFilePath = join(dirname(directory), ".env");
|
const envFilePath = join(dirname(directory), ".env");
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
defaultMariadbEnv,
|
defaultMariadbEnv,
|
||||||
mariadb.environment.project.env,
|
mariadb.environment.project.env,
|
||||||
|
mariadb.environment.env,
|
||||||
);
|
);
|
||||||
const volumesMount = generateVolumeMounts(mounts);
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
const bindsMount = generateBindMounts(mounts);
|
const bindsMount = generateBindMounts(mounts);
|
||||||
|
|||||||
@ -103,6 +103,7 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
defaultMongoEnv,
|
defaultMongoEnv,
|
||||||
mongo.environment.project.env,
|
mongo.environment.project.env,
|
||||||
|
mongo.environment.env,
|
||||||
);
|
);
|
||||||
const volumesMount = generateVolumeMounts(mounts);
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
const bindsMount = generateBindMounts(mounts);
|
const bindsMount = generateBindMounts(mounts);
|
||||||
|
|||||||
@ -61,6 +61,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
defaultMysqlEnv,
|
defaultMysqlEnv,
|
||||||
mysql.environment.project.env,
|
mysql.environment.project.env,
|
||||||
|
mysql.environment.env,
|
||||||
);
|
);
|
||||||
const volumesMount = generateVolumeMounts(mounts);
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
const bindsMount = generateBindMounts(mounts);
|
const bindsMount = generateBindMounts(mounts);
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
defaultPostgresEnv,
|
defaultPostgresEnv,
|
||||||
postgres.environment.project.env,
|
postgres.environment.project.env,
|
||||||
|
postgres.environment.env,
|
||||||
);
|
);
|
||||||
const volumesMount = generateVolumeMounts(mounts);
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
const bindsMount = generateBindMounts(mounts);
|
const bindsMount = generateBindMounts(mounts);
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
defaultRedisEnv,
|
defaultRedisEnv,
|
||||||
redis.environment.project.env,
|
redis.environment.project.env,
|
||||||
|
redis.environment.env,
|
||||||
);
|
);
|
||||||
const volumesMount = generateVolumeMounts(mounts);
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
const bindsMount = generateBindMounts(mounts);
|
const bindsMount = generateBindMounts(mounts);
|
||||||
|
|||||||
@ -259,21 +259,44 @@ export const removeService = async (
|
|||||||
export const prepareEnvironmentVariables = (
|
export const prepareEnvironmentVariables = (
|
||||||
serviceEnv: string | null,
|
serviceEnv: string | null,
|
||||||
projectEnv?: string | null,
|
projectEnv?: string | null,
|
||||||
|
environmentEnv?: string | null,
|
||||||
) => {
|
) => {
|
||||||
const projectVars = parse(projectEnv ?? "");
|
const projectVars = parse(projectEnv ?? "");
|
||||||
|
const environmentVars = parse(environmentEnv ?? "");
|
||||||
const serviceVars = parse(serviceEnv ?? "");
|
const serviceVars = parse(serviceEnv ?? "");
|
||||||
|
|
||||||
const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
|
const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
|
||||||
let resolvedValue = value;
|
let resolvedValue = value;
|
||||||
|
|
||||||
|
// Replace project variables
|
||||||
if (projectVars) {
|
if (projectVars) {
|
||||||
resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
|
resolvedValue = resolvedValue.replace(
|
||||||
if (projectVars[ref] !== undefined) {
|
/\$\{\{project\.(.*?)\}\}/g,
|
||||||
return projectVars[ref];
|
(_, ref) => {
|
||||||
}
|
if (projectVars[ref] !== undefined) {
|
||||||
throw new Error(`Invalid project environment variable: project.${ref}`);
|
return projectVars[ref];
|
||||||
});
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Invalid project environment variable: project.${ref}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace environment variables
|
||||||
|
if (environmentVars) {
|
||||||
|
resolvedValue = resolvedValue.replace(
|
||||||
|
/\$\{\{environment\.(.*?)\}\}/g,
|
||||||
|
(_, ref) => {
|
||||||
|
if (environmentVars[ref] !== undefined) {
|
||||||
|
return environmentVars[ref];
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid environment variable: environment.${ref}`);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace self-references (service variables)
|
||||||
resolvedValue = resolvedValue.replace(/\$\{\{(.*?)\}\}/g, (_, ref) => {
|
resolvedValue = resolvedValue.replace(/\$\{\{(.*?)\}\}/g, (_, ref) => {
|
||||||
if (serviceVars[ref] !== undefined) {
|
if (serviceVars[ref] !== undefined) {
|
||||||
return serviceVars[ref];
|
return serviceVars[ref];
|
||||||
@ -301,8 +324,9 @@ export const parseEnvironmentKeyValuePair = (
|
|||||||
export const getEnviromentVariablesObject = (
|
export const getEnviromentVariablesObject = (
|
||||||
input: string | null,
|
input: string | null,
|
||||||
projectEnv?: string | null,
|
projectEnv?: string | null,
|
||||||
|
environmentEnv?: string | null,
|
||||||
) => {
|
) => {
|
||||||
const envs = prepareEnvironmentVariables(input, projectEnv);
|
const envs = prepareEnvironmentVariables(input, projectEnv, environmentEnv);
|
||||||
|
|
||||||
const jsonObject: Record<string, string> = {};
|
const jsonObject: Record<string, string> = {};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user