From d471ed83f1289f3234db79ae02b6ee1f6b54cf50 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Mon, 14 Jan 2019 07:53:42 +0100
Subject: [PATCH] feat: deprecate platform tokens (#3067)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Deprecate use of “special” env var like `GITHUB_TOKEN` and instead standardize on `RENOVATE_*` environment variables instead.

Closes #2834

BREAKING CHANGE: For GitHub, GitLab, Bitbucket and VSTS you need to migrate `*_ENDPOINT` to `RENOVATE_ENDPOINT`, `*_TOKEN` to `RENOVATE_TOKEN`, and same for `BITBUCKET_USERNAME` and `BITBUCKET_PASSWORD`.
---
 docs/local-development.md                  |  5 +-
 docs/self-hosting.md                       | 24 ++----
 lib/config/env.js                          | 73 ++++-------------
 lib/config/index.js                        | 37 +++++----
 test/config/__snapshots__/env.spec.js.snap | 91 ++++------------------
 test/config/env.spec.js                    | 40 ++++------
 test/config/index.spec.js                  | 22 ++++--
 test/platform/github/index.spec.js         |  2 +-
 test/platform/gitlab/index.spec.js         |  2 +-
 9 files changed, 90 insertions(+), 206 deletions(-)

diff --git a/docs/local-development.md b/docs/local-development.md
index ee25e1ca2c..e81a97743e 100644
--- a/docs/local-development.md
+++ b/docs/local-development.md
@@ -42,10 +42,7 @@ Once you have decided on your platform and account, log in and [generate a "Pers
 #### Export platform token
 
 Although you can specify a token to Renovate using `--token=`, it can be inconvenient if you need to include this every time.
-You are better off to instead export an Environment Variable for this.
-
-If your platform of choice is GitHub, then export `GITHUB_TOKEN`, and if it's GitLab then export `GITLAB_TOKEN`.
-It's also find to export both so that you can switch between platforms.
+You are better off to instead export the Environment Variable `RENOVATE_TOKEN` for this.
 
 #### Run against a real repo
 
diff --git a/docs/self-hosting.md b/docs/self-hosting.md
index 830271754a..e9aef69cb6 100644
--- a/docs/self-hosting.md
+++ b/docs/self-hosting.md
@@ -117,22 +117,9 @@ You can find instructions for VSTS
 [vsts](https://www.visualstudio.com/en-us/docs/integrate/get-started/authentication/pats).
 
 This token needs to be configured via file, environment variable, or CLI. See
-[docs/configuration.md](configuration.md) for details. The simplest way is
-to expose it as `GITHUB_TOKEN`, `GITLAB_TOKEN` or `VSTS_TOKEN`.
+[docs/configuration.md](configuration.md) for details. The simplest way is to expose it as `RENOVATE_TOKEN`.
 
-For Bitbucket, you can configure `BITBUCKET_USERNAME` and `BITBUCKET_PASSWORD`, or combine them together yourself into `BITBUCKET_TOKEN` using the node REPL:
-
-```
-const btoa = str => Buffer.from(str, 'binary').toString('base64');
-
-btoa(`${user}:${bbaAppPassword}`)
-```
-
-You must then expose either the token or username + password to your env, or provide them via the CLI. Example:
-
-```sh
-renovate --platform=bitbucket --username=rarkins --password=ABCDEFghijklmop123 rarkins/testrepo1
-```
+For Bitbucket, you can configure `RENOVATE_USERNAME` and `RENOVATE_PASSWORD`.
 
 ## Usage
 
@@ -171,15 +158,14 @@ Most people will run Renovate via cron, e.g. once per hour. Here is an example b
 
 export PATH="/home/user/.yarn/bin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
 export RENOVATE_CONFIG_FILE="/home/user/renovate-config.js"
-export GITHUB_TOKEN="**github-token**" # Delete this if using GitHub Enterprise
-export GITLAB_TOKEN="**github-token**" # Delete this if using GitHub
-export GITHUB_COM_TOKEN="**github-token**" # Delete this if using GitLab or github.com
+export RENOVATE_TOKEN="**some-token**" # GitHub, GitLab, Azure DevOps or BitBucket
+export GITHUB_COM_TOKEN="**github-token**" # Delete this if using github.com
 
 # Renovate
 renovate
 ```
 
-Note: the GitHub token in env is necessary in order to retrieve Release Notes that are hosted on github.com. Use `GITHUB_COM_TOKEN` if running against GitHub Enterprise or `GITHUB_TOKEN` if running against GitLab. i.e. remove one of the lines as applicable.
+Note: the GitHub.com token in env is necessary in order to retrieve Release Notes that are usually hosted on github.com. You don't need to add it if you are already running the bot against github.com, but you do need to add it if you're using GitHub Enterprise, GitLab, Azure DevOps, or Bitbucket.
 
 You should save and test out this script manually first, and add it to cron once you've verified it.
 
diff --git a/lib/config/env.js b/lib/config/env.js
index 18fe916216..d60fa36a87 100644
--- a/lib/config/env.js
+++ b/lib/config/env.js
@@ -39,16 +39,7 @@ function getConfig(env) {
     }
   });
 
-  if (env.GITHUB_TOKEN && env.GITHUB_TOKEN !== 'dummy') {
-    config.hostRules.push({
-      platform: 'github',
-      endpoint: env.GITHUB_ENDPOINT,
-      token: env.GITHUB_TOKEN,
-      default: true,
-    });
-  }
-
-  if (env.GITHUB_COM_TOKEN && env.GITHUB_TOKEN !== 'dummy') {
+  if (env.GITHUB_COM_TOKEN) {
     config.hostRules.push({
       endpoint: 'https://api.github.com/',
       platform: 'github',
@@ -56,37 +47,6 @@ function getConfig(env) {
     });
   }
 
-  if (env.GITLAB_TOKEN) {
-    config.hostRules.push({
-      platform: 'gitlab',
-      endpoint: env.GITLAB_ENDPOINT,
-      token: env.GITLAB_TOKEN,
-    });
-  }
-
-  if (env.BITBUCKET_TOKEN) {
-    config.hostRules.push({
-      platform: 'bitbucket',
-      endpoint: env.BITBUCKET_ENDPOINT,
-      token: env.BITBUCKET_TOKEN,
-    });
-  } else if (env.BITBUCKET_USERNAME && env.BITBUCKET_PASSWORD) {
-    const base64 = str => Buffer.from(str, 'binary').toString('base64');
-    config.hostRules.push({
-      platform: 'bitbucket',
-      endpoint: env.BITBUCKET_ENDPOINT,
-      token: base64(`${env.BITBUCKET_USERNAME}:${env.BITBUCKET_PASSWORD}`),
-    });
-  }
-
-  if (env.VSTS_ENDPOINT || env.VSTS_TOKEN) {
-    config.hostRules.push({
-      platform: 'vsts',
-      endpoint: env.VSTS_ENDPOINT,
-      token: env.VSTS_TOKEN,
-    });
-  }
-
   if (env.DOCKER_USERNAME && env.DOCKER_PASSWORD) {
     config.hostRules.push({
       platform: 'docker',
@@ -95,23 +55,20 @@ function getConfig(env) {
     });
   }
 
-  if (config.platform === 'gitlab') {
-    config.endpoint = env.GITLAB_ENDPOINT;
-  } else if (config.platform === 'vsts') {
-    config.endpoint = env.VSTS_ENDPOINT;
-  } else if (env.GITHUB_ENDPOINT) {
-    // GitHub is default
-    config.endpoint = env.GITHUB_ENDPOINT;
-  }
-
-  /* eslint-disable no-param-reassign */
-  delete env.GITHUB_TOKEN;
-  delete env.GITHUB_ENDPOINT;
-  delete env.GITHUB_COM_TOKEN;
-  delete env.GITLAB_TOKEN;
-  delete env.GITLAB_ENDPOINT;
-  delete env.VSTS_TOKEN;
-  delete env.VSTS_ENDPOINT;
+  // These env vars are deprecated and deleted to make sure they're not used
+  const unsupportedEnv = [
+    'BITBUCKET_TOKEN',
+    'BITBUCKET_USERNAME',
+    'BITBUCKET_PASSWORD',
+    'GITHUB_ENDPOINT',
+    'GITHUB_TOKEN',
+    'GITLAB_ENDPOINT',
+    'GITLAB_TOKEN',
+    'VSTS_ENDPOINT',
+    'VSTS_TOKEN',
+  ];
+  // eslint-disable-next-line no-param-reassign
+  unsupportedEnv.forEach(val => delete env[val]);
 
   return config;
 }
diff --git a/lib/config/index.js b/lib/config/index.js
index a7713b96e6..eeb7722067 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -83,12 +83,31 @@ async function parseConfigs(env, argv) {
   // Get global config
   logger.trace({ config }, 'Full config');
 
-  // Check platforms and tokens
-  const { platform, endpoint, username, password, token } = config;
+  // Check platform and authentication
+  const { platform, username, password } = config;
   const platformInfo = hostRules.defaults[platform];
   if (!platformInfo) {
     throw new Error(`Unsupported platform: ${config.platform}.`);
   }
+  const endpoint = config.endpoint || platformInfo.endpoint;
+  let token = config.token;
+  if (!token) {
+    if (username && password) {
+      const base64 = str => Buffer.from(str, 'binary').toString('base64');
+      token = base64(`${username}:${password}`);
+    } else {
+      throw new Error(
+        `No authentication found for platform ${endpoint} (${platform})`
+      );
+    }
+  }
+  config.hostRules.push({
+    platform,
+    endpoint,
+    username,
+    password,
+    token,
+  });
   config.hostRules.forEach(hostRules.update);
   delete config.hostRules;
   delete config.token;
@@ -104,21 +123,7 @@ async function parseConfigs(env, argv) {
     }
   );
 
-  if (
-    platform === 'bitbucket' &&
-    !credentials.token &&
-    (username && password)
-  ) {
-    logger.debug('Found configured username && password');
-    const base64 = str => Buffer.from(str, 'binary').toString('base64');
-    credentials.token = base64(`${username}:${password}`);
-  }
-
   if (!global.appMode) {
-    if (!credentials.token) {
-      throw new Error(`You need to supply a ${platformInfo.name} token.`);
-    }
-
     hostRules.update({
       ...credentials,
       default: true,
diff --git a/test/config/__snapshots__/env.spec.js.snap b/test/config/__snapshots__/env.spec.js.snap
index bfe82efcf2..219281be7b 100644
--- a/test/config/__snapshots__/env.spec.js.snap
+++ b/test/config/__snapshots__/env.spec.js.snap
@@ -2,27 +2,21 @@
 
 exports[`config/env .getConfig(env) supports Bitbucket token 1`] = `
 Object {
-  "hostRules": Array [
-    Object {
-      "endpoint": "a bitbucket endpoint",
-      "platform": "bitbucket",
-      "token": "a bitbucket token",
-    },
-  ],
+  "endpoint": "a bitbucket endpoint",
+  "hostRules": Array [],
+  "password": "app-password",
   "platform": "bitbucket",
+  "username": "some-username",
 }
 `;
 
 exports[`config/env .getConfig(env) supports Bitbucket username/password 1`] = `
 Object {
-  "hostRules": Array [
-    Object {
-      "endpoint": "a bitbucket endpoint",
-      "platform": "bitbucket",
-      "token": "c29tZS11c2VybmFtZTphcHAtcGFzc3dvcmQ=",
-    },
-  ],
+  "endpoint": "a bitbucket endpoint",
+  "hostRules": Array [],
+  "password": "app-password",
   "platform": "bitbucket",
+  "username": "some-username",
 }
 `;
 
@@ -37,97 +31,46 @@ exports[`config/env .getConfig(env) supports GitHub custom endpoint and github.c
 Object {
   "endpoint": "a ghe endpoint",
   "hostRules": Array [
-    Object {
-      "default": true,
-      "endpoint": "a ghe endpoint",
-      "platform": "github",
-      "token": "a ghe token",
-    },
     Object {
       "endpoint": "https://api.github.com/",
       "platform": "github",
       "token": "a github.com token",
     },
   ],
-}
-`;
-
-exports[`config/env .getConfig(env) supports GitHub custom endpoint and github.com and gitlab.com 1`] = `
-Object {
-  "endpoint": "a ghe endpoint",
-  "hostRules": Array [
-    Object {
-      "default": true,
-      "endpoint": "a ghe endpoint",
-      "platform": "github",
-      "token": "a ghe token",
-    },
-    Object {
-      "endpoint": "https://api.github.com/",
-      "platform": "github",
-      "token": "a github.com token",
-    },
-    Object {
-      "endpoint": undefined,
-      "platform": "gitlab",
-      "token": "a gitlab token",
-    },
-  ],
+  "token": "a ghe token",
 }
 `;
 
 exports[`config/env .getConfig(env) supports GitHub token 1`] = `
 Object {
-  "hostRules": Array [
-    Object {
-      "default": true,
-      "endpoint": undefined,
-      "platform": "github",
-      "token": "github.com token",
-    },
-  ],
+  "hostRules": Array [],
+  "token": "github.com token",
 }
 `;
 
 exports[`config/env .getConfig(env) supports GitLab custom endpoint 1`] = `
 Object {
   "endpoint": "a gitlab endpoint",
-  "hostRules": Array [
-    Object {
-      "endpoint": "a gitlab endpoint",
-      "platform": "gitlab",
-      "token": "a gitlab token",
-    },
-  ],
+  "hostRules": Array [],
   "platform": "gitlab",
+  "token": "a gitlab token",
 }
 `;
 
 exports[`config/env .getConfig(env) supports GitLab token 1`] = `
 Object {
-  "endpoint": undefined,
-  "hostRules": Array [
-    Object {
-      "endpoint": undefined,
-      "platform": "gitlab",
-      "token": "a gitlab.com token",
-    },
-  ],
+  "hostRules": Array [],
   "platform": "gitlab",
+  "token": "a gitlab.com token",
 }
 `;
 
 exports[`config/env .getConfig(env) supports VSTS 1`] = `
 Object {
   "endpoint": "a vsts endpoint",
-  "hostRules": Array [
-    Object {
-      "endpoint": "a vsts endpoint",
-      "platform": "vsts",
-      "token": "a vsts token",
-    },
-  ],
+  "hostRules": Array [],
   "platform": "vsts",
+  "token": "a vsts token",
 }
 `;
 
diff --git a/test/config/env.spec.js b/test/config/env.spec.js
index e62472d390..0ed3d0e44b 100644
--- a/test/config/env.spec.js
+++ b/test/config/env.spec.js
@@ -35,50 +35,41 @@ describe('config/env', () => {
       expect(env.getConfig(envParam).lockFileMaintenance).toEqual({});
     });
     it('supports GitHub token', () => {
-      const envParam = { GITHUB_TOKEN: 'github.com token' };
+      const envParam = { RENOVATE_TOKEN: 'github.com token' };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
     it('supports GitHub custom endpoint', () => {
-      const envParam = { GITHUB_ENDPOINT: 'a ghe endpoint' };
+      const envParam = { RENOVATE_ENDPOINT: 'a ghe endpoint' };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
     it('supports GitHub custom endpoint and github.com', () => {
       const envParam = {
         GITHUB_COM_TOKEN: 'a github.com token',
-        GITHUB_ENDPOINT: 'a ghe endpoint',
-        GITHUB_TOKEN: 'a ghe token',
-      };
-      expect(env.getConfig(envParam)).toMatchSnapshot();
-    });
-    it('supports GitHub custom endpoint and github.com and gitlab.com', () => {
-      const envParam = {
-        GITHUB_COM_TOKEN: 'a github.com token',
-        GITHUB_ENDPOINT: 'a ghe endpoint',
-        GITHUB_TOKEN: 'a ghe token',
-        GITLAB_TOKEN: 'a gitlab token',
+        RENOVATE_ENDPOINT: 'a ghe endpoint',
+        RENOVATE_TOKEN: 'a ghe token',
       };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
     it('supports GitLab token', () => {
       const envParam = {
         RENOVATE_PLATFORM: 'gitlab',
-        GITLAB_TOKEN: 'a gitlab.com token',
+        RENOVATE_TOKEN: 'a gitlab.com token',
       };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
     it('supports GitLab custom endpoint', () => {
       const envParam = {
         RENOVATE_PLATFORM: 'gitlab',
-        GITLAB_TOKEN: 'a gitlab token',
-        GITLAB_ENDPOINT: 'a gitlab endpoint',
+        RENOVATE_TOKEN: 'a gitlab token',
+        RENOVATE_ENDPOINT: 'a gitlab endpoint',
       };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
     it('supports VSTS', () => {
       const envParam = {
         RENOVATE_PLATFORM: 'vsts',
-        VSTS_TOKEN: 'a vsts token',
-        VSTS_ENDPOINT: 'a vsts endpoint',
+        RENOVATE_TOKEN: 'a vsts token',
+        RENOVATE_ENDPOINT: 'a vsts endpoint',
       };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
@@ -92,19 +83,18 @@ describe('config/env', () => {
     it('supports Bitbucket token', () => {
       const envParam = {
         RENOVATE_PLATFORM: 'bitbucket',
-        BITBUCKET_TOKEN: 'a bitbucket token',
-        BITBUCKET_ENDPOINT: 'a bitbucket endpoint',
-        BITBUCKET_USERNAME: 'some-username',
-        BITBUCKET_PASSWORD: 'app-password',
+        RENOVATE_ENDPOINT: 'a bitbucket endpoint',
+        RENOVATE_USERNAME: 'some-username',
+        RENOVATE_PASSWORD: 'app-password',
       };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
     it('supports Bitbucket username/password', () => {
       const envParam = {
         RENOVATE_PLATFORM: 'bitbucket',
-        BITBUCKET_ENDPOINT: 'a bitbucket endpoint',
-        BITBUCKET_USERNAME: 'some-username',
-        BITBUCKET_PASSWORD: 'app-password',
+        RENOVATE_ENDPOINT: 'a bitbucket endpoint',
+        RENOVATE_USERNAME: 'some-username',
+        RENOVATE_PASSWORD: 'app-password',
       };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
diff --git a/test/config/index.spec.js b/test/config/index.spec.js
index b7d41362a4..a1f83b43bb 100644
--- a/test/config/index.spec.js
+++ b/test/config/index.spec.js
@@ -52,7 +52,9 @@ describe('config/index', () => {
       } catch (e) {
         err = e;
       }
-      expect(err.message).toBe('You need to supply a GitHub token.');
+      expect(err.message).toBe(
+        'No authentication found for platform https://api.github.com/ (github)'
+      );
     });
     it('throws for no GitLab token', async () => {
       const env = { RENOVATE_PLATFORM: 'gitlab' };
@@ -62,7 +64,9 @@ describe('config/index', () => {
       } catch (e) {
         err = e;
       }
-      expect(err.message).toBe('You need to supply a GitLab token.');
+      expect(err.message).toBe(
+        'No authentication found for platform https://gitlab.com/api/v4/ (gitlab)'
+      );
     });
     it('throws for no vsts token', async () => {
       const env = { RENOVATE_PLATFORM: 'vsts' };
@@ -72,10 +76,12 @@ describe('config/index', () => {
       } catch (e) {
         err = e;
       }
-      expect(err.message).toBe('You need to supply a VSTS token.');
+      expect(err.message).toBe(
+        'No authentication found for platform undefined (vsts)'
+      );
     });
     it('supports token in env', async () => {
-      const env = { GITHUB_TOKEN: 'abc' };
+      const env = { RENOVATE_TOKEN: 'abc' };
       await configParser.parseConfigs(env, defaultArgv);
     });
     it('supports token in CLI options', async () => {
@@ -85,7 +91,7 @@ describe('config/index', () => {
     });
     it('supports forceCli', async () => {
       defaultArgv = defaultArgv.concat(['--force-cli=true']);
-      const env = { GITHUB_TOKEN: 'abc' };
+      const env = { RENOVATE_TOKEN: 'abc' };
       await configParser.parseConfigs(env, defaultArgv);
     });
     it('supports Bitbucket username/passwod', async () => {
@@ -173,7 +179,7 @@ describe('config/index', () => {
       expect(vstsApi.gitApi.mock.calls.length).toBe(1);
     });
     it('logs if no autodiscovered repositories', async () => {
-      const env = { GITHUB_TOKEN: 'abc' };
+      const env = { RENOVATE_TOKEN: 'abc' };
       defaultArgv = defaultArgv.concat(['--autodiscover']);
       ghGot.mockImplementationOnce(() => ({
         headers: {},
@@ -208,7 +214,7 @@ describe('config/index', () => {
       ).toMatchSnapshot();
     });
     it('adds a log file', async () => {
-      const env = { GITHUB_TOKEN: 'abc', RENOVATE_LOG_FILE: 'debug.log' };
+      const env = { RENOVATE_TOKEN: 'abc', RENOVATE_LOG_FILE: 'debug.log' };
       defaultArgv = defaultArgv.concat(['--autodiscover']);
       ghGot.mockImplementationOnce(() => ({
         headers: {},
@@ -221,7 +227,7 @@ describe('config/index', () => {
     it('resolves all presets', async () => {
       defaultArgv.push('--pr-hourly-limit=10', '--automerge=false');
       const env = {
-        GITHUB_TOKEN: 'abc',
+        RENOVATE_TOKEN: 'abc',
         RENOVATE_CONFIG_FILE: require.resolve(
           '../_fixtures/config/file-with-repo-presets.js'
         ),
diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js
index 7d07145af2..af301ed9f7 100644
--- a/test/platform/github/index.spec.js
+++ b/test/platform/github/index.spec.js
@@ -82,7 +82,7 @@ describe('platform/github', () => {
     ].forEach(([envToken, token, endpoint], i) => {
       it(`should initialise the config for the repo - ${i}`, async () => {
         if (envToken !== undefined) {
-          process.env.GITHUB_TOKEN = envToken;
+          process.env.RENOVATE_TOKEN = envToken;
         }
         const config = await initRepo({
           repository: 'some/repo',
diff --git a/test/platform/gitlab/index.spec.js b/test/platform/gitlab/index.spec.js
index 6269424b1b..c30baa3a15 100644
--- a/test/platform/gitlab/index.spec.js
+++ b/test/platform/gitlab/index.spec.js
@@ -106,7 +106,7 @@ describe('platform/gitlab', () => {
     ].forEach(([envToken, token, endpoint, gitAuthor], i) => {
       it(`should initialise the config for the repo - ${i}`, async () => {
         if (envToken !== undefined) {
-          process.env.GITLAB_TOKEN = envToken;
+          process.env.RENOVATE_TOKEN = envToken;
         }
         get.mockReturnValue({ body: [] });
         const config = await initRepo({
-- 
GitLab