mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 15:06:27 +00:00
feat(template): use environment variables in templates (#19301)
This commit is contained in:
parent
662fe78958
commit
2cd10769f2
5 changed files with 55 additions and 3 deletions
|
@ -363,8 +363,15 @@ If this option is not set, Renovate will fallback to 15 minutes.
|
||||||
## exposeAllEnv
|
## exposeAllEnv
|
||||||
|
|
||||||
To keep you safe, Renovate only passes a limited set of environment variables to package managers.
|
To keep you safe, Renovate only passes a limited set of environment variables to package managers.
|
||||||
Confidential data can be leaked if a malicious script enumerates all environment variables.
|
If you must expose all environment variables to package managers, you can set this option to `true`.
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
!!! warning
|
||||||
|
Always consider the security implications of using `exposeAllEnv`!
|
||||||
|
Secrets and other confidential information stored in environment variables could be leaked by a malicious script, that enumerates all environment variables.
|
||||||
|
|
||||||
Set `exposeAllEnv` to `true` only if you have reviewed, and trust, the repositories which Renovate bot runs against.
|
Set `exposeAllEnv` to `true` only if you have reviewed, and trust, the repositories which Renovate bot runs against.
|
||||||
|
Alternatively, you can use the [`customEnvVariables`](https://docs.renovatebot.com/self-hosted-configuration/#customenvvariables) config option to handpick a set of variables you need to expose.
|
||||||
|
|
||||||
Setting this to `true` also allows for variable substitution in `.npmrc` files.
|
Setting this to `true` also allows for variable substitution in `.npmrc` files.
|
||||||
|
|
||||||
|
|
|
@ -95,3 +95,14 @@ In the example above, it will only show a text if `isMajor=true` and `hasRelease
|
||||||
Returns `true` if at least one expression is `true`.
|
Returns `true` if at least one expression is `true`.
|
||||||
|
|
||||||
`{{#if (or isPatch isSingleVersion}}Small update, safer to merge and release.{{else}}Check out the changelog for all versions before merging!{{/if}}`
|
`{{#if (or isPatch isSingleVersion}}Small update, safer to merge and release.{{else}}Check out the changelog for all versions before merging!{{/if}}`
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
By default, you can only access a handful of basic environment variables like `HOME` or `PATH`.
|
||||||
|
This is for security reasons.
|
||||||
|
|
||||||
|
`HOME is {{env.HOME}}`
|
||||||
|
|
||||||
|
If you're self-hosting Renovate, you can expose additional variables with the [`customEnvVariables`](https://docs.renovatebot.com/self-hosted-configuration/#customenvvariables) config option.
|
||||||
|
|
||||||
|
You can also use the [`exposeAllEnv`](https://docs.renovatebot.com/self-hosted-configuration/#exposeallenv) config option to allow all environment variables in templates, but make sure to consider the security implications of giving the scripts unrestricted access to all variables.
|
||||||
|
|
|
@ -21,7 +21,7 @@ import type {
|
||||||
RawExecOptions,
|
RawExecOptions,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
function getChildEnv({
|
export function getChildEnv({
|
||||||
extraEnv,
|
extraEnv,
|
||||||
env: forcedEnv = {},
|
env: forcedEnv = {},
|
||||||
}: ExecOptions): Record<string, string> {
|
}: ExecOptions): Record<string, string> {
|
||||||
|
|
|
@ -1,7 +1,20 @@
|
||||||
|
import { mocked } from '../../../test/util';
|
||||||
import { getOptions } from '../../config/options';
|
import { getOptions } from '../../config/options';
|
||||||
|
import * as _exec from '../exec';
|
||||||
import * as template from '.';
|
import * as template from '.';
|
||||||
|
|
||||||
|
jest.mock('../exec');
|
||||||
|
|
||||||
|
const exec = mocked(_exec);
|
||||||
|
|
||||||
describe('util/template/index', () => {
|
describe('util/template/index', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
exec.getChildEnv.mockReturnValue({
|
||||||
|
CUSTOM_FOO: 'foo',
|
||||||
|
HOME: '/root',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('has valid exposed config options', () => {
|
it('has valid exposed config options', () => {
|
||||||
const allOptions = getOptions().map((option) => option.name);
|
const allOptions = getOptions().map((option) => option.name);
|
||||||
const missingOptions = template.exposedConfigOptions.filter(
|
const missingOptions = template.exposedConfigOptions.filter(
|
||||||
|
@ -85,6 +98,18 @@ describe('util/template/index', () => {
|
||||||
expect(output).toBe('foo');
|
expect(output).toBe('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('has access to basic environment variables (basicEnvVars)', () => {
|
||||||
|
const userTemplate = 'HOME is {{env.HOME}}';
|
||||||
|
const output = template.compile(userTemplate, {});
|
||||||
|
expect(output).toBe('HOME is /root');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('and has access to custom variables (customEnvVariables)', () => {
|
||||||
|
const userTemplate = 'CUSTOM_FOO is {{env.CUSTOM_FOO}}';
|
||||||
|
const output = template.compile(userTemplate, {});
|
||||||
|
expect(output).toBe('CUSTOM_FOO is foo');
|
||||||
|
});
|
||||||
|
|
||||||
describe('proxyCompileInput', () => {
|
describe('proxyCompileInput', () => {
|
||||||
const allowedField = 'body';
|
const allowedField = 'body';
|
||||||
const forbiddenField = 'foobar';
|
const forbiddenField = 'foobar';
|
||||||
|
|
|
@ -2,6 +2,7 @@ import is from '@sindresorhus/is';
|
||||||
import handlebars from 'handlebars';
|
import handlebars from 'handlebars';
|
||||||
import { GlobalConfig } from '../../config/global';
|
import { GlobalConfig } from '../../config/global';
|
||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
|
import { getChildEnv } from '../exec';
|
||||||
|
|
||||||
handlebars.registerHelper('encodeURIComponent', encodeURIComponent);
|
handlebars.registerHelper('encodeURIComponent', encodeURIComponent);
|
||||||
handlebars.registerHelper('decodeURIComponent', decodeURIComponent);
|
handlebars.registerHelper('decodeURIComponent', decodeURIComponent);
|
||||||
|
@ -170,6 +171,10 @@ const allowedTemplateFields = new Set([
|
||||||
|
|
||||||
const compileInputProxyHandler: ProxyHandler<CompileInput> = {
|
const compileInputProxyHandler: ProxyHandler<CompileInput> = {
|
||||||
get(target: CompileInput, prop: keyof CompileInput): unknown {
|
get(target: CompileInput, prop: keyof CompileInput): unknown {
|
||||||
|
if (prop === 'env') {
|
||||||
|
return target[prop];
|
||||||
|
}
|
||||||
|
|
||||||
if (!allowedTemplateFields.has(prop)) {
|
if (!allowedTemplateFields.has(prop)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -202,13 +207,17 @@ export function compile(
|
||||||
input: CompileInput,
|
input: CompileInput,
|
||||||
filterFields = true
|
filterFields = true
|
||||||
): string {
|
): string {
|
||||||
const data = { ...GlobalConfig.get(), ...input };
|
const env = getChildEnv({});
|
||||||
|
const data = { ...GlobalConfig.get(), ...input, env };
|
||||||
const filteredInput = filterFields ? proxyCompileInput(data) : data;
|
const filteredInput = filterFields ? proxyCompileInput(data) : data;
|
||||||
logger.trace({ template, filteredInput }, 'Compiling template');
|
logger.trace({ template, filteredInput }, 'Compiling template');
|
||||||
if (filterFields) {
|
if (filterFields) {
|
||||||
const matches = template.matchAll(templateRegex);
|
const matches = template.matchAll(templateRegex);
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const varNames = match[1].split('.');
|
const varNames = match[1].split('.');
|
||||||
|
if (varNames[0] === 'env') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (const varName of varNames) {
|
for (const varName of varNames) {
|
||||||
if (!allowedFieldsList.includes(varName)) {
|
if (!allowedFieldsList.includes(varName)) {
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
Loading…
Reference in a new issue