Skip to content

Commit

Permalink
Extract image pull to a separate service class
Browse files Browse the repository at this point in the history
Extract logic to pull images from the updater class.
  • Loading branch information
Philip Harrison committed Jul 26, 2021
1 parent 85169c2 commit 7ac744a
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 71 deletions.
36 changes: 20 additions & 16 deletions __tests__/updater-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import Docker from 'dockerode'
import fs from 'fs'
import path from 'path'
import {Updater} from '../src/updater'
import {UPDATER_IMAGE_NAME} from '../src/main'
import {ImageService} from '../src/image-service'

describe('Updater', () => {
const docker = new Docker()
// To run the js-code itself against API:
// const params = {
// jobID: 1,
Expand All @@ -14,7 +15,7 @@ describe('Updater', () => {
// }
// const client = axios.create({baseURL: params.dependabotAPI})
// const api = new DependabotAPI(client, params)
// const updater = new Updater(docker, api)
// const updater = new Updater(UPDATER_IMAGE_NAME, api)

// This stubs out API calls from JS, but will run the updater against an API
// running on the specified API endpoint.
Expand All @@ -28,26 +29,29 @@ describe('Updater', () => {
dependabotAPIURL: 'http://host.docker.internal:3001'
}
}
const updater = new Updater(docker, mockAPIClient)
const updater = new Updater(UPDATER_IMAGE_NAME, mockAPIClient)

beforeAll(() => {
updater.pullImage()
beforeAll(async () => {
await ImageService.pullImage(UPDATER_IMAGE_NAME)
})

afterEach(() => {
docker.listContainers(function (err, containers) {
if (!containers) return
afterEach(async () => {
const docker = new Docker()
const containers = (await docker.listContainers()) || []

for (const container of containers) {
if (
container.Image.includes(
'docker.pkg.github.com/dependabot/dependabot-updater'
)
) {
docker.getContainer(container.Id).remove()
for (const container of containers) {
if (
container.Image.includes(
'docker.pkg.github.com/dependabot/dependabot-updater'
)
) {
try {
await docker.getContainer(container.Id).remove({v: true, force: true})
} catch (e) {
// ignore
}
}
})
}
})

jest.setTimeout(20000)
Expand Down
5 changes: 2 additions & 3 deletions __tests__/updater.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Docker from 'dockerode'
import {UPDATER_IMAGE_NAME} from '../src/main'
import {Updater} from '../src/updater'

describe('Updater', () => {
const docker = new Docker()
const mockAPIClient: any = {
getJobDetails: jest.fn(),
getCredentials: jest.fn(),
Expand All @@ -13,7 +12,7 @@ describe('Updater', () => {
dependabotAPIURL: 'http://localhost'
}
}
const updater = new Updater(docker, mockAPIClient)
const updater = new Updater(UPDATER_IMAGE_NAME, mockAPIClient)

it('should fetch job details', async () => {
mockAPIClient.getJobDetails.mockImplementation(() => {
Expand Down
38 changes: 38 additions & 0 deletions src/image-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as core from '@actions/core'
import Docker from 'dockerode'
import {Readable} from 'stream'

const endOfStream = async (docker: Docker, stream: Readable): Promise<void> => {
return new Promise((resolve, reject) => {
docker.modem.followProgress(stream, (err: Error) =>
err ? reject(err) : resolve(undefined)
)
})
}

export const ImageService = {
/** Fetch the configured updater image, if it isn't already available. */
async pullImage(imageName: string, force = false): Promise<void> {
const docker = new Docker()
try {
const image = await docker.getImage(imageName).inspect()
if (!force) {
core.info(`Resolved ${imageName} to existing ${image.Id}`)
return
} // else fallthrough to pull
} catch (e) {
if (!e.message.includes('no such image')) {
throw e
} // else fallthrough to pull
}

core.info(`Pulling image ${imageName}...`)
const auth = {
username: process.env.GITHUB_PKG_USER,
password: process.env.GITHUB_PKG_TOKEN
}
const stream = await docker.pull(imageName, {authconfig: auth})
await endOfStream(docker, stream)
core.info(`Pulled image ${imageName}`)
}
}
12 changes: 7 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import {getJobParameters} from './inputs'
import Docker from 'dockerode'
import {ImageService} from './image-service'
import {Updater} from './updater'
import {APIClient} from './api-client'
import axios from 'axios'

export const UPDATER_IMAGE_NAME =
'docker.pkg.github.com/dependabot/dependabot-updater:latest'

async function run(): Promise<void> {
try {
// Decode JobParameters:
Expand All @@ -16,11 +19,10 @@ async function run(): Promise<void> {
core.setSecret(params.jobToken)
core.setSecret(params.credentialsToken)

const docker = new Docker()
const client = axios.create({baseURL: params.dependabotAPIURL})
const api = new APIClient(client, params)
const updater = new Updater(docker, api)
await updater.pullImage()
const apiClient = new APIClient(client, params)
const updater = new Updater(UPDATER_IMAGE_NAME, apiClient)
await ImageService.pullImage(UPDATER_IMAGE_NAME)

await updater.runUpdater()
} catch (error) {
Expand Down
58 changes: 11 additions & 47 deletions src/updater.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,26 @@
import * as core from '@actions/core'
import * as Docker from 'dockerode'
import Docker, {Container} from 'dockerode'
import path from 'path'
import fs from 'fs'
import {Credential, JobDetails, APIClient} from './api-client'
import {Readable} from 'stream'
import {pack} from 'tar-stream'

const JOB_INPUT_FILENAME = 'job.json'
const JOB_INPUT_PATH = `/home/dependabot/dependabot-updater`

const JOB_OUTPUT_FILENAME = 'output.json'
const JOB_OUTPUT_PATH = '/home/dependabot/dependabot-updater/output'
const REPO_CONTENTS_PATH = '/home/dependabot/dependabot-updater/repo'
const DEFAULT_UPDATER_IMAGE =
'docker.pkg.github.com/dependabot/dependabot-updater:latest'

const decode = (str: string): string =>
Buffer.from(str, 'base64').toString('binary')

export class Updater {
docker: Docker
constructor(
private readonly docker: Docker,
private readonly apiClient: APIClient,
private readonly updaterImage = DEFAULT_UPDATER_IMAGE
) {}

/** Fetch the configured updater image, if it isn't already available. */
async pullImage(force = false): Promise<void> {
try {
const image = await this.docker.getImage(this.updaterImage).inspect()
if (!force) {
core.info(`Resolved ${this.updaterImage} to existing ${image.Id}`)
return
} // else fallthrough to pull
} catch (e) {
if (!e.message.includes('no such image')) {
throw e
} // else fallthrough to pull
}

core.info(`Pulling image ${this.updaterImage}...`)
const auth = {
username: process.env.GITHUB_PKG_USER,
password: process.env.GITHUB_PKG_TOKEN
}
const stream = await this.docker.pull(this.updaterImage, {authconfig: auth})
await this.endOfStream(stream)
core.info(`Pulled image ${this.updaterImage}`)
}

private async endOfStream(stream: Readable): Promise<void> {
return new Promise((resolve, reject) => {
this.docker.modem.followProgress(stream, (err: Error) =>
err ? reject(err) : resolve(undefined)
)
})
private readonly updaterImage: string,
private readonly apiClient: APIClient
) {
this.docker = new Docker()
}

/**
Expand All @@ -65,7 +31,7 @@ export class Updater {
const details = await this.apiClient.getJobDetails()
const credentials = await this.apiClient.getCredentials()
// TODO: once the proxy is set up, remove credentials from the job details
details['credentials'] = credentials
details.credentials = credentials

const files = await this.runFileFetcher(details, credentials)
if (!files) {
Expand Down Expand Up @@ -121,7 +87,7 @@ export class Updater {
details: JobDetails,
files: FetchedFiles
): Promise<void> {
core.info(`running update ${details.id} ${files}`)
core.info(`Running update job ${this.apiClient.params.jobID}`)
const container = await this.createContainer('update_files')
const containerInput: FileUpdaterInput = {
base_commit_sha: files.base_commit_sha,
Expand All @@ -133,9 +99,7 @@ export class Updater {
await this.runContainer(container)
}

private async createContainer(
updaterCommand: string
): Promise<Docker.Container> {
private async createContainer(updaterCommand: string): Promise<Container> {
const container = await this.docker.createContainer({
Image: this.updaterImage,
AttachStdout: true,
Expand All @@ -162,7 +126,7 @@ export class Updater {
}

private async storeContainerInput(
container: Docker.Container,
container: Container,
input: FileFetcherInput | FileUpdaterInput
): Promise<void> {
const tar = pack()
Expand All @@ -171,7 +135,7 @@ export class Updater {
await container.putArchive(tar, {path: JOB_INPUT_PATH})
}

private async runContainer(container: Docker.Container): Promise<void> {
private async runContainer(container: Container): Promise<void> {
try {
await container.start()
const stream = await container.attach({
Expand Down

0 comments on commit 7ac744a

Please sign in to comment.