Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Use the ImageService to fetch docker dependencies in CI, dev
As a preamble to pinning the image versions we use this introduces
`npm run fetch-images` as a way to pre-pull the images defined in docker_tags.ts
which we will set to specific SHAs in future versions.

This ensures CI and developers pull the images before attempting to run the code
to avoid any surprise breakages.

It also makes the presence of a GITHUB_TOKEN envvar a validation check in
ImageService.pull to avoid confusing docker errors if it isn't present.

Finally, it avoids passing any auth credentials to non-GitHub hosts when
we run our tests

Co-Authored by: Philip Harrison <philip@mailharrison.com>
  • Loading branch information
Barry Gordon authored and Barry Gordon committed Feb 15, 2022
1 parent c46e41a commit 88dd91b
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 39 deletions.
16 changes: 7 additions & 9 deletions .github/workflows/integration-test.yml
Expand Up @@ -13,15 +13,6 @@ jobs:
integration:
runs-on: ubuntu-latest
steps:
- name: GPR login
run: docker login docker.pkg.github.com -u x -p ${{ secrets.GITHUB_TOKEN }}

- name: GRP pull dependabot/dependabot-updater
run: docker pull docker.pkg.github.com/dependabot/dependabot-updater:v1

- name: GRP pull github/dependabot-update-job-proxy
run: docker pull docker.pkg.github.com/github/dependabot-update-job-proxy:v1

- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
Expand All @@ -38,5 +29,12 @@ jobs:
- name: Install NPM dependencies
run: npm ci

- name: Pre-fetch the pinned images
run: npm run fetch-images
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Run integration tests
run: npm run test-integration
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -78,6 +78,9 @@ typings/
.env
.env.test

# asdf tool version
.tool-versions

# parcel-bundler cache (https://parceljs.org/)
.cache

Expand Down
7 changes: 5 additions & 2 deletions __tests__/container-service.test.ts
Expand Up @@ -7,9 +7,13 @@ describe('ContainerService', () => {
const docker = new Docker()
let container: any

beforeAll(async () => {
/* We use alpine as a small, easy-to-script-for test stand-in for the updater */
await ImageService.fetchImage('alpine')
})

describe('when a container runs successfully', () => {
beforeEach(async () => {
await ImageService.pull('alpine')
container = await docker.createContainer({
Image: 'alpine',
AttachStdout: true,
Expand All @@ -26,7 +30,6 @@ describe('ContainerService', () => {

describe('when a container runs unsuccessfully', () => {
beforeEach(async () => {
await ImageService.pull('alpine')
container = await docker.createContainer({
Image: 'alpine',
AttachStdout: true,
Expand Down
15 changes: 15 additions & 0 deletions __tests__/docker-tags.test.ts
@@ -0,0 +1,15 @@
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from '../src/docker-tags'

describe('Docker tags', () => {
test('UPDATER_IMAGE_NAME', () => {
expect(UPDATER_IMAGE_NAME).toMatch(
/^docker\.pkg\.github\.com\/dependabot\/dependabot-updater:v1$/
)
})

test('PROXY_IMAGE_NAME', () => {
expect(PROXY_IMAGE_NAME).toMatch(
/^docker\.pkg\.github\.com\/github\/dependabot-update-job-proxy:v1$/
)
})
})
2 changes: 1 addition & 1 deletion __tests__/helpers.ts
@@ -1,5 +1,5 @@
import Docker from 'dockerode'
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from '../src/main'
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from '../src/docker-tags'
import waitPort from 'wait-port'
import path from 'path'
import {spawn} from 'child_process'
Expand Down
46 changes: 44 additions & 2 deletions __tests__/image-service.test.ts
@@ -1,7 +1,49 @@
import {ImageService} from '../src/image-service'

describe('ImageService', () => {
test('pulls the image from docker hub', async () => {
await ImageService.pull('hello-world')
const originalEnv = process.env

describe('when GITHUB_TOKEN is not set', () => {
beforeEach(async () => {
jest.resetModules()
process.env = {
...originalEnv,
GITHUB_TOKEN: undefined
}
})

afterEach(async () => {
process.env = originalEnv
})

test('it raises an error', async () => {
await expect(
ImageService.pull('ghcr.io/dependabot/dependabot-core:latest')
).rejects.toThrowError(
new Error('No GITHUB_TOKEN set, unable to pull images.')
)
})
})

describe('when asked to fetch non-GitHub hosted images', () => {
beforeEach(async () => {
jest.resetModules()
process.env = {
...originalEnv,
GITHUB_TOKEN: 'mock_token'
}
})

afterEach(async () => {
process.env = originalEnv
})

test('it raises an error', async () => {
await expect(ImageService.pull('hello-world')).rejects.toThrowError(
new Error(
'Only images distributed via docker.pkg.github.com or ghcr.io can be fetched'
)
)
})
})
})
2 changes: 1 addition & 1 deletion __tests__/proxy-integration.test.ts
@@ -1,7 +1,7 @@
import Docker from 'dockerode'
import {Credential} from '../src/api-client'
import {ImageService} from '../src/image-service'
import {PROXY_IMAGE_NAME} from '../src/main'
import {PROXY_IMAGE_NAME} from '../src/docker-tags'
import {ProxyBuilder} from '../src/proxy'
import {integration, removeDanglingUpdaterContainers} from './helpers'
import {spawnSync} from 'child_process'
Expand Down
2 changes: 1 addition & 1 deletion __tests__/updater-builder-integration.test.ts
@@ -1,4 +1,4 @@
import {PROXY_IMAGE_NAME, UPDATER_IMAGE_NAME} from '../src/main'
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from '../src/docker-tags'
import {ImageService} from '../src/image-service'
import {removeDanglingUpdaterContainers, integration} from './helpers'
import Docker from 'dockerode'
Expand Down
2 changes: 1 addition & 1 deletion __tests__/updater-integration.test.ts
Expand Up @@ -4,7 +4,7 @@ import path from 'path'
import {ApiClient} from '../src/api-client'
import {ImageService} from '../src/image-service'
import {JobParameters} from '../src/inputs'
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from '../src/main'
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from '../src/docker-tags'
import {Updater} from '../src/updater'

import {
Expand Down
53 changes: 43 additions & 10 deletions dist/main/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/main/index.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -13,8 +13,8 @@
"test": "SKIP_INTEGRATION_TESTS=true jest --detectOpenHandles",
"test-integration": "jest --detectOpenHandles 'integration'",
"prepare": "husky install",
"all": "npm run format && npm run lint && npm run package && npm test",
"dependabot": "npx ts-node src/cli.ts"
"dependabot": "ts-node src/cli.ts",
"fetch-images": "ts-node src/fetch-images.ts"
},
"repository": {
"type": "git",
Expand Down
4 changes: 4 additions & 0 deletions src/docker-tags.ts
@@ -0,0 +1,4 @@
export const UPDATER_IMAGE_NAME =
'docker.pkg.github.com/dependabot/dependabot-updater:v1'
export const PROXY_IMAGE_NAME =
'docker.pkg.github.com/github/dependabot-update-job-proxy:v1'
9 changes: 9 additions & 0 deletions src/fetch-images.ts
@@ -0,0 +1,9 @@
import {ImageService} from './image-service'
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from './docker-tags'

export async function run(): Promise<void> {
await ImageService.pull(UPDATER_IMAGE_NAME)
await ImageService.pull(PROXY_IMAGE_NAME)
}

run()
36 changes: 34 additions & 2 deletions src/image-service.ts
Expand Up @@ -10,9 +10,32 @@ const endOfStream = async (docker: Docker, stream: Readable): Promise<void> => {
})
}

/** Fetch the configured updater image, if it isn't already available. */
export const ImageService = {
/** Fetch the configured updater image, if it isn't already available. */
async pull(imageName: string, force = false): Promise<void> {
/*
This method fetches images using a GITHUB_TOKEN we should check two things:
- The process has a GITHUB_TOKEN set so we don't attempt a failed call to docker
- The image being requested is actually hosted on GitHub.
We expose the `fetch_image` utility method to allow us to pull in arbitrary images
without auth in unit tests.
*/
if (
!(
imageName.startsWith('ghcr.io/') ||
imageName.startsWith('docker.pkg.github.com/')
)
) {
throw new Error(
'Only images distributed via docker.pkg.github.com or ghcr.io can be fetched'
)
}

if (!process.env.GITHUB_TOKEN) {
throw new Error('No GITHUB_TOKEN set, unable to pull images.')
}

const docker = new Docker()
try {
const image = await docker.getImage(imageName).inspect()
Expand All @@ -26,11 +49,20 @@ export const ImageService = {
} // else fallthrough to pull
}

core.info(`Pulling image ${imageName}...`)
const auth = {
username: 'x',
password: process.env.GITHUB_TOKEN
}
this.fetchImage(imageName, auth, docker)
},

/* Retrieve the imageName using the auth details provided, if any */
async fetchImage(
imageName: string,
auth = {},
docker = new Docker()
): Promise<void> {
core.info(`Pulling image ${imageName}...`)
const stream = await docker.pull(imageName, {authconfig: auth})
await endOfStream(docker, stream)
core.info(`Pulled image ${imageName}`)
Expand Down
10 changes: 3 additions & 7 deletions src/main.ts
@@ -1,16 +1,12 @@
import axios from 'axios'
import * as core from '@actions/core'
import * as github from '@actions/github'
import {Context} from '@actions/github/lib/context'
import {ApiClient, CredentialFetchingError} from './api-client'
import {getJobParameters} from './inputs'
import {ImageService} from './image-service'
import {UPDATER_IMAGE_NAME, PROXY_IMAGE_NAME} from './docker-tags'
import {Updater, UpdaterFetchError} from './updater'
import {ApiClient, CredentialFetchingError} from './api-client'
import axios from 'axios'

export const UPDATER_IMAGE_NAME =
'docker.pkg.github.com/dependabot/dependabot-updater:v1'
export const PROXY_IMAGE_NAME =
'docker.pkg.github.com/github/dependabot-update-job-proxy:v1'

export enum DependabotErrorType {
Unknown = 'actions_workflow_unknown',
Expand Down

0 comments on commit 88dd91b

Please sign in to comment.