From 008f05800b08ef4296e7d4e665d84dce123df774 Mon Sep 17 00:00:00 2001 From: Jeroen de Bruijn Date: Sat, 6 Mar 2021 21:18:58 +0100 Subject: [PATCH] feat: add passing of Renovate environment variables Inputs may now be provided using environment variables, as well as the existing inputs. None of the inputs are required any more, so it is possible to use only environment variables. Nevertheless all inputs must be provided in some way, either using the input or their corresponding environment variables. BREAKING CHANGE: The `configurationFile` input no longer has a default value. This means that a value for it is now required using the `configurationFile` input or the `RENOVATE_CONFIG_FILE` environment variable. Closes #136. --- .github/workflows/build.yml | 2 + README.md | 14 ++++--- action.yml | 10 +++-- example/renovate-config.js | 1 - example/renovate.json | 1 - src/index.ts | 2 +- src/input.ts | 74 +++++++++++++++++++++++++++++++++---- src/renovate.ts | 48 +++++++++++++----------- 8 files changed, 110 insertions(+), 42 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc056529..105709c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,6 +48,8 @@ jobs: run: npm run build - name: Renovate test uses: ./ + env: + LOG_LEVEL: debug with: configurationFile: ${{ matrix.configurationFile }} token: ${{ secrets.RENOVATE_TOKEN }} diff --git a/README.md b/README.md index 57b02b03..528c5db8 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ GitHub Action to run Renovate self-hosted. ## Options +Options can be passed using the inputs of this action or the corresponding environment variables. When both are passed, the input takes precedence over the environment variable. For the available environment variables see the Renovate [Self-Hosted Configuration](https://docs.renovatebot.com/self-hosted-configuration/) and [Self-Hosting](https://docs.renovatebot.com/self-hosting/) docs. + ## `configurationFile` Configuration file to configure Renovate. The supported configurations files can be one of the configuration files listed in the Renovate Docs for [Configuration Options](https://docs.renovatebot.com/configuration-options/) or a JavaScript file that exports a configuration object. For both of these options, an example can be found in the [example](./example) directory. @@ -80,11 +82,11 @@ jobs: Instead of using a Personal Access Token (PAT) that is tied to a particular user you can use a [GitHub App](https://docs.github.com/en/developers/apps/building-github-apps) where permissions can be even better tuned. [Create a new app](https://docs.github.com/en/developers/apps/creating-a-github-app) and give it the following permissions: -| Permission | Level | -|-----------------|---------------------| -| `Contents` | `Read & write` | -| `Metadata` | `Read-only` | -| `Pull requests` | `Read & write` | +| Permission | Level | +| --------------- | -------------- | +| `Contents` | `Read & write` | +| `Metadata` | `Read-only` | +| `Pull requests` | `Read & write` | Store the app ID as a secret with name `APP_ID` and generate a new private key for the app and add it as a secret to the repository as `APP_PEM` in the repository where the action will run from. Note that `APP_PEM` needs to be base64 encoded. You can encode your private key file like this from the terminal: @@ -121,5 +123,5 @@ jobs: uses: renovatebot/github-action@v21.30.0 with: configurationFile: example/renovate-config.js - token: "x-access-token:${{ steps.get_token.outputs.app_token }}" + token: 'x-access-token:${{ steps.get_token.outputs.app_token }}' ``` diff --git a/action.yml b/action.yml index f48c435d..749d2362 100644 --- a/action.yml +++ b/action.yml @@ -6,14 +6,16 @@ branding: color: blue inputs: configurationFile: - description: 'Configuration file to configure Renovate' + description: | + Configuration file to configure Renovate. Either use this input or the + 'RENOVATE_CONFIG_FILE' environment variable. required: false - default: src/config.js token: description: | GitHub personal access token that Renovate should use. This should be - configured using a Secret. - required: true + configured using a Secret. Either use this input or the 'RENOVATE_TOKEN' + environment variable. + required: false runs: using: node12 main: dist/index.js diff --git a/example/renovate-config.js b/example/renovate-config.js index 202b8c60..c38fdf8a 100644 --- a/example/renovate-config.js +++ b/example/renovate-config.js @@ -2,7 +2,6 @@ module.exports = { branchPrefix: 'test-renovate/', dryRun: true, gitAuthor: 'Renovate Bot ', - logLevel: 'debug', onboarding: false, platform: 'github', includeForks: true, diff --git a/example/renovate.json b/example/renovate.json index dd8a5ab4..b04579ed 100644 --- a/example/renovate.json +++ b/example/renovate.json @@ -2,7 +2,6 @@ "branchPrefix": "test-renovate/", "dryRun": true, "gitAuthor": "Renovate Bot ", - "logLevel": "debug", "onboarding": false, "platform": "github", "includeForks": true, diff --git a/src/index.ts b/src/index.ts index 012c7f52..a1ea74e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import Renovate from './renovate'; async function run(): Promise { try { const input = new Input(); - const renovate = new Renovate(input.configurationFile, input.token); + const renovate = new Renovate(input); await renovate.runDockerContainer(); } catch (error) { diff --git a/src/input.ts b/src/input.ts index 207a394c..6b07dce7 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,20 +1,78 @@ import * as core from '@actions/core'; +import path from 'path'; + +interface EnvironmentVariable { + key: string; + value: string; +} class Input { - readonly configurationFile = core.getInput('configurationFile', { - required: true, - }); - readonly token = core.getInput('token', { required: true }); + readonly options = { + envRegex: /^(?:RENOVATE_\w+|LOG_LEVEL)$/, + configurationFile: { + input: 'configurationFile', + env: 'RENOVATE_CONFIG_FILE', + }, + token: { + input: 'token', + env: 'RENOVATE_TOKEN', + }, + } as const; + + private readonly _environmentVariables: Map; constructor() { - this.validate(); + this._environmentVariables = new Map( + Object.entries(process.env).filter(([key]) => + this.options.envRegex.test(key) + ) + ); + + this.setEnvironmentVariable( + this.options.token.input, + this.options.token.env + ); + this.setEnvironmentVariable( + this.options.configurationFile.input, + this.options.configurationFile.env + ); } - validate(): void { - if (this.token === '') { - throw new Error('input.token MUST NOT be empty'); + configurationFile(): string { + return path.resolve( + this._environmentVariables.get(this.options.configurationFile.env) + ); + } + + /** + * Convert to environment variables. + * + * @note The environment variable for the configuration file is filtered out + * and is available with `configurationFile()` instead. + */ + toEnvironmentVariables(): EnvironmentVariable[] { + return [...this._environmentVariables].map(([key, value]) => ({ + key, + value, + })); + } + + private setEnvironmentVariable(input: string, env: string) { + const optionalInput = core.getInput(input); + if (optionalInput === '' && !this._environmentVariables.has(env)) { + throw new Error( + [ + `'${input}' MUST be passed using its input or the '${env}'`, + 'environment variable', + ].join(' ') + ); + } + + if (optionalInput !== '') { + this._environmentVariables.set(env, optionalInput); } } } export default Input; +export { EnvironmentVariable, Input }; diff --git a/src/renovate.ts b/src/renovate.ts index 0f83d5c9..767ab6ec 100644 --- a/src/renovate.ts +++ b/src/renovate.ts @@ -1,20 +1,16 @@ import Docker from './docker'; +import { Input } from './input'; import { exec } from '@actions/exec'; import fs from 'fs'; import path from 'path'; class Renovate { - private configFileEnv = 'RENOVATE_CONFIG_FILE'; - private tokenEnv = 'RENOVATE_TOKEN'; private dockerGroupName = 'docker'; private configFileMountDir = '/github-action'; - private configFile: string; private docker: Docker; - constructor(configFile: string, private token: string) { - this.configFile = path.resolve(configFile); - + constructor(private input: Input) { this.validateArguments(); this.docker = new Docker(); @@ -22,18 +18,28 @@ class Renovate { async runDockerContainer(): Promise { const renovateDockerUser = 'ubuntu'; - const githubActionsDockerGroupId = this.getDockerGroupId(); - const commandArguments = [ - '--rm', - `--env ${this.configFileEnv}=${this.configFileMountPath()}`, - `--env ${this.tokenEnv}=${this.token}`, - `--volume ${this.configFile}:${this.configFileMountPath()}`, - `--volume /var/run/docker.sock:/var/run/docker.sock`, - `--volume /tmp:/tmp`, - `--user ${renovateDockerUser}:${githubActionsDockerGroupId}`, - this.docker.image(), - ]; - const command = `docker run ${commandArguments.join(' ')}`; + + const dockerArguments = [ + ...this.input.toEnvironmentVariables(), + { + key: this.input.options.configurationFile.env, + value: this.configFileMountPath(), + }, + ] + .map((e) => { + const quotedValue = /\s/.test(e.value) ? `'${e.value}'` : e.value; + return `--env ${e.key}=${quotedValue}`; + }) + .concat([ + `--volume ${this.input.configurationFile()}:${this.configFileMountPath()}`, + '--volume /var/run/docker.sock:/var/run/docker.sock', + '--volume /tmp:/tmp', + `--user ${renovateDockerUser}:${this.getDockerGroupId()}`, + '--rm', + this.docker.image(), + ]); + + const command = `docker run ${dockerArguments.join(' ')}`; const code = await exec(command); if (code !== 0) { @@ -71,15 +77,15 @@ class Renovate { } private validateArguments(): void { - if (!fs.existsSync(this.configFile)) { + if (!fs.existsSync(this.input.configurationFile())) { throw new Error( - `Could not locate configuration file '${this.configFile}'.` + `Could not locate configuration file '${this.input.configurationFile()}'.` ); } } private configFileName(): string { - return path.basename(this.configFile); + return path.basename(this.input.configurationFile()); } private configFileMountPath(): string {