Skip to content
Permalink
Browse files
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 88dd91b7a55db2d90e43fc89f66fbe487cd40528
Showing 16 changed files with 130 additions and 28 deletions.
@@ -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 }}
@@ -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 }}
@@ -78,6 +78,9 @@ typings/
.env
.env.test

# asdf tool version
.tool-versions

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

@@ -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,
@@ -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,
@@ -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$/
)
})
})
@@ -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'
@@ -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'
)
)
})
})
})
@@ -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'
@@ -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'
@@ -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 {

Some generated files are not rendered by default. Learn more.

Large diffs are not rendered by default.

@@ -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",
@@ -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'
@@ -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()
@@ -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()
@@ -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}`)
@@ -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',

0 comments on commit 88dd91b

Please sign in to comment.