diff --git a/README.md b/README.md index 07a626748..5c3c2ef0e 100644 --- a/README.md +++ b/README.md @@ -537,6 +537,10 @@ You can pass environment variables; the easiest way to do it is via a `.env` fil ``` SETTINGS_FILE_PATH=settings.yml ``` +1. Read the configs from a different git ref of the admin repo during scheduled and full syncs with `CONFIG_REF` (default is the default branch). Webhook-triggered syncs are not affected. For example: + ``` + CONFIG_REF=my-config-branch + ``` 1. Configure the deployment settings file path using `DEPLOYMENT_CONFIG_FILE` (default is `deployment-settings.yml`). For e.g. ``` DEPLOYMENT_CONFIG_FILE=deployment-settings.yml diff --git a/index.js b/index.js index 12aae7c79..2993e1ebe 100644 --- a/index.js +++ b/index.js @@ -242,7 +242,9 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => log: robot.log, repo: () => { return { repo: env.ADMIN_REPO, owner: installation.account.login } } } - return syncAllSettings(nop, context) + // CONFIG_REF lets scheduled/CLI syncs read the config from a non-default + // ref of the admin repo (e.g. to test config changes from a branch). + return syncAllSettings(nop, context, context.repo(), env.CONFIG_REF) } return null } diff --git a/lib/env.js b/lib/env.js index 8ed5d927e..565e8e430 100644 --- a/lib/env.js +++ b/lib/env.js @@ -1,6 +1,7 @@ module.exports = { ADMIN_REPO: process.env.ADMIN_REPO || 'admin', CONFIG_PATH: process.env.CONFIG_PATH || '.github', + CONFIG_REF: process.env.CONFIG_REF, SETTINGS_FILE_PATH: process.env.SETTINGS_FILE_PATH || 'settings.yml', DEPLOYMENT_CONFIG_FILE_PATH: process.env.DEPLOYMENT_CONFIG_FILE || 'deployment-settings.yml', CREATE_PR_COMMENT: process.env.CREATE_PR_COMMENT || 'true', @@ -8,5 +9,5 @@ module.exports = { BLOCK_REPO_RENAME_BY_HUMAN: process.env.BLOCK_REPO_RENAME_BY_HUMAN || 'false', FULL_SYNC_NOP: process.env.FULL_SYNC_NOP === 'true', GHE_HOST: process.env.GHE_HOST, - GHE_PROTOCOL: process.env.GHE_PROTOCOL, + GHE_PROTOCOL: process.env.GHE_PROTOCOL } diff --git a/test/unit/index.syncInstallation.test.js b/test/unit/index.syncInstallation.test.js new file mode 100644 index 000000000..ebbe237a7 --- /dev/null +++ b/test/unit/index.syncInstallation.test.js @@ -0,0 +1,83 @@ +/* eslint-disable no-undef */ + +// Tests for the syncInstallation path in index.js, which serves scheduled +// (CRON) and full-sync runs. The app factory takes Settings as an injectable +// parameter, so the sync can be observed without touching the GitHub API. +describe('syncInstallation', () => { + const installation = { id: 123, account: { login: 'test-org' } } + let robot + let settingsMock + + function buildRobot () { + const content = Buffer.from('restrictedRepos: []').toString('base64') + const github = { + paginate: jest.fn().mockResolvedValue([installation]), + rest: { + apps: { + listInstallations: { endpoint: { merge: jest.fn().mockReturnValue({}) } }, + getAuthenticated: jest.fn().mockResolvedValue({ data: { slug: 'safe-settings' } }) + }, + repos: { + getContent: jest.fn().mockResolvedValue({ data: { content } }) + } + } + } + return { + log: Object.assign(jest.fn(), { + debug: jest.fn(), + trace: jest.fn(), + info: jest.fn(), + error: jest.fn() + }), + auth: jest.fn().mockResolvedValue(github), + on: jest.fn(), + github + } + } + + function loadApp () { + let app + jest.isolateModules(() => { + const appFn = require('../../index') + app = appFn(robot, {}, settingsMock) + }) + return app + } + + beforeEach(() => { + robot = buildRobot() + settingsMock = { + syncAll: jest.fn().mockResolvedValue({}), + handleError: jest.fn().mockResolvedValue({}) + } + }) + + afterEach(() => { + delete process.env.CONFIG_REF + }) + + describe('when CONFIG_REF is not set', () => { + it('reads the config from the default branch and passes no ref to syncAll', async () => { + const app = loadApp() + + await app.syncInstallation() + + expect(robot.github.rest.repos.getContent).toHaveBeenCalledWith(expect.objectContaining({ ref: undefined })) + expect(settingsMock.syncAll).toHaveBeenCalledTimes(1) + expect(settingsMock.syncAll.mock.calls[0][4]).toBeUndefined() + }) + }) + + describe('when CONFIG_REF is set', () => { + it('reads the config from that ref and passes it through to syncAll', async () => { + process.env.CONFIG_REF = 'my-config-branch' + const app = loadApp() + + await app.syncInstallation() + + expect(robot.github.rest.repos.getContent).toHaveBeenCalledWith(expect.objectContaining({ ref: 'my-config-branch' })) + expect(settingsMock.syncAll).toHaveBeenCalledTimes(1) + expect(settingsMock.syncAll.mock.calls[0][4]).toBe('my-config-branch') + }) + }) +})