mirror of
https://github.com/louislam/dockge.git
synced 2026-05-21 14:02:17 +00:00
Add envsubst
This commit is contained in:
139
backend/envsubst.ts
Normal file
139
backend/envsubst.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Original Source: https://github.com/inventage/envsubst/blob/main/src/utils.js
|
||||
* MIT License
|
||||
* Copyright (c) 2021 Inventage AG
|
||||
*
|
||||
* Copy this file, because
|
||||
*/
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { LooseObject } from "./util-common";
|
||||
|
||||
const toLowerKeys = (object : LooseObject) => {
|
||||
return Object.keys(object).reduce((accumulator : LooseObject, key) => {
|
||||
accumulator[key.toLowerCase()] = object[key];
|
||||
return accumulator;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Regex pattern with an optional prefix.
|
||||
*
|
||||
* @see https://regex101.com/r/M3dVAW/1
|
||||
* @param prefix
|
||||
* @returns {string}
|
||||
*/
|
||||
const variableRegexPattern = (prefix = ""): string => {
|
||||
return `\\\${(${prefix ? escapeStringRegexp(prefix) : ""}\\w+)(:-([^}]*))?}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Regex pattern that wraps the variable regex pattern with a window variable statement:
|
||||
*
|
||||
* window['${VAR}'] or window["${VAR}"]
|
||||
*
|
||||
* @see https://regex101.com/r/ND057d/1
|
||||
* @param prefix
|
||||
* @returns {string}
|
||||
*/
|
||||
const windowVariableRegexPattern = (prefix = ""): string => {
|
||||
return `(window\\[['"]{1})?${variableRegexPattern(prefix)}(['"]{1}\\])?`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces all variable placeholders in the given string with either variable values
|
||||
* found in the variables parameter OR with the given default in the variable string.
|
||||
*
|
||||
* @param {string} string
|
||||
* @param {object} variables
|
||||
* @param {string} prefix
|
||||
* @param {boolean} trimWindow
|
||||
* @param {boolean} ignoreCase
|
||||
* @returns {Promise<unknown[]>}
|
||||
*/
|
||||
const replaceVariables = (string: string, variables: object = {}, prefix: string = "", trimWindow: boolean = false, ignoreCase: boolean = false): Promise<unknown[]> =>
|
||||
new Promise(resolve => {
|
||||
resolve(replaceVariablesSync(string, variables, prefix, trimWindow, ignoreCase));
|
||||
});
|
||||
|
||||
/**
|
||||
* Replaces all variable placeholders in the given string with either variable values
|
||||
* found in the variables parameter OR with the given default in the variable string.
|
||||
*
|
||||
* @param {string} string
|
||||
* @param {object} variables
|
||||
* @param {string} prefix
|
||||
* @param {boolean} trimWindow
|
||||
* @param {boolean} ignoreCase
|
||||
* @returns {unknown[]}
|
||||
*/
|
||||
const replaceVariablesSync = (string : string, variables: LooseObject = {}, prefix: string = "", trimWindow: boolean = false, ignoreCase: boolean = false): unknown[] => {
|
||||
const regex = new RegExp(trimWindow ? windowVariableRegexPattern(prefix) : variableRegexPattern(prefix), ignoreCase ? "gmi" : "gm");
|
||||
const matches = [ ...string.matchAll(regex) ];
|
||||
const lowercaseVariables = toLowerKeys(variables);
|
||||
|
||||
let replaced = string;
|
||||
const replacements : LooseObject[] = [];
|
||||
for (const match of matches) {
|
||||
if (trimWindow) {
|
||||
const [ original, windowStart, name, , fallback, windowEnd ] = match;
|
||||
|
||||
// Bail if the match does not contain `^window[`
|
||||
if (!windowStart) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const valueStartQuote = windowStart.replace("window[", "");
|
||||
const valueEndQuote = windowEnd.replace("]", "");
|
||||
const withoutWindow = original.replace(windowStart, "").replace(windowEnd, "");
|
||||
|
||||
let value;
|
||||
if (ignoreCase) {
|
||||
value = Object.hasOwnProperty.call(lowercaseVariables || {}, name.toLowerCase()) ? lowercaseVariables[name.toLowerCase()] : fallback;
|
||||
} else {
|
||||
value = Object.hasOwnProperty.call(variables || {}, name) ? variables[name] : fallback;
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
const quotedValue = `${valueStartQuote}${value}${valueEndQuote}`;
|
||||
const replacement = replacements.find(r => r.from === original && r.to === quotedValue);
|
||||
if (replacement) {
|
||||
replacement.count = replacement.count + 1;
|
||||
} else {
|
||||
replacements.push({ from: original,
|
||||
to: quotedValue,
|
||||
count: 1 });
|
||||
}
|
||||
|
||||
replaced = replaced.split(original).join(withoutWindow.split(withoutWindow).join(quotedValue));
|
||||
}
|
||||
} else {
|
||||
const [ original, name, , fallback ] = match;
|
||||
|
||||
let value : string;
|
||||
if (ignoreCase) {
|
||||
value = Object.hasOwnProperty.call(lowercaseVariables || {}, name.toLowerCase()) ? lowercaseVariables[name.toLowerCase()] : fallback;
|
||||
} else {
|
||||
value = Object.hasOwnProperty.call(variables || {}, name) ? variables[name] : fallback;
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
const replacement = replacements.find(r => r.from === original && r.to === value);
|
||||
if (replacement) {
|
||||
replacement.count = replacement.count + 1;
|
||||
} else {
|
||||
replacements.push({ from: original,
|
||||
to: value,
|
||||
count: 1 });
|
||||
}
|
||||
|
||||
replaced = replaced.split(original).join(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ replaced, replacements ];
|
||||
};
|
||||
|
||||
|
||||
|
||||
export { variableRegexPattern, replaceVariables, replaceVariablesSync };
|
||||
@@ -2,15 +2,15 @@
|
||||
* Common utilities for backend and frontend
|
||||
*/
|
||||
import yaml, { Document, Pair, Scalar } from "yaml";
|
||||
import dotenv, { DotenvParseOutput } from "dotenv";
|
||||
// @ts-ignore
|
||||
import envsub from "envsub";
|
||||
import { DotenvParseOutput } from "dotenv";
|
||||
|
||||
// Init dayjs
|
||||
import dayjs from "dayjs";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
// @ts-ignore
|
||||
import { replaceVariablesSync } from "@inventage/envsubst";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
@@ -345,13 +345,18 @@ export function parseDockerPort(input : string, defaultHostname : string = "loca
|
||||
};
|
||||
}
|
||||
|
||||
export function envsubst(string : string, variables : LooseObject) : string {
|
||||
return replaceVariablesSync(string, variables)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse all values in the yaml and for each value, if there are template variables, replace it environment variables
|
||||
* Emulates the behavior of how docker-compose handles environment variables in yaml files
|
||||
* @param content Yaml string
|
||||
* @param env Environment variables
|
||||
* @returns string Yaml string with environment variables replaced
|
||||
*/
|
||||
export function renderYAML(content : string, env : DotenvParseOutput) : string {
|
||||
export function envsubstYAML(content : string, env : DotenvParseOutput) : string {
|
||||
const doc = yaml.parseDocument(content);
|
||||
if (doc.contents) {
|
||||
// @ts-ignore
|
||||
@@ -362,7 +367,12 @@ export function renderYAML(content : string, env : DotenvParseOutput) : string {
|
||||
return doc.toString();
|
||||
}
|
||||
|
||||
export function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
|
||||
/**
|
||||
* Used for envsubstYAML(...)
|
||||
* @param pair
|
||||
* @param env
|
||||
*/
|
||||
function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
|
||||
// @ts-ignore
|
||||
if (pair.value && pair.value.items) {
|
||||
// @ts-ignore
|
||||
@@ -370,45 +380,12 @@ export function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
|
||||
if (item instanceof Pair) {
|
||||
traverseYAML(item, env);
|
||||
} else if (item instanceof Scalar) {
|
||||
item.value = "CAN_READ!";
|
||||
item.value = envsubst(item.value, env);
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
} else if (pair.value && typeof(pair.value.value) === "string") {
|
||||
// @ts-ignore
|
||||
pair.value.value = "CAN_READ!";
|
||||
pair.value.value = envsubst(pair.value.value, env);
|
||||
}
|
||||
}
|
||||
|
||||
const config = dotenv.parse(`TEST=123
|
||||
`);
|
||||
|
||||
let test = renderYAML(`
|
||||
x-dockge:
|
||||
icon: null
|
||||
author: null
|
||||
repo: ""
|
||||
a: 1\${C}
|
||||
urls:
|
||||
- https://louislam.net:3000/test.php?aaa=232&bbb=23
|
||||
- http://uptime.kuma.pet
|
||||
- ""
|
||||
version: "3.8"
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080\${C:-:}80
|
||||
environment: []
|
||||
networks: []
|
||||
depends_on: []
|
||||
nginx2:
|
||||
image: nginx:latest
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
asdsd: {}
|
||||
`, config);
|
||||
|
||||
console.log(test);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user