Skip to content

Commit

Permalink
Merge pull request #223 from nitrictech/feature/container-builds
Browse files Browse the repository at this point in the history
Container build refactor
  • Loading branch information
jyecusch authored Oct 26, 2021
2 parents 337a299 + 2973d11 commit 4b3ad01
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 61 deletions.
2 changes: 1 addition & 1 deletion packages/common/src/stack/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class StackContainer<ContainerExtensions = Record<string, any>> {
);
}

return origPath;
return fullPath;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/common/src/stack/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,10 @@ export const STACK_SCHEMA: JSONSchema7 = {
properties: {
dockerfile: { type: 'string' },
context: { type: 'string' },
args: {
type: 'object',
additionalProperties: { type: 'string' },
},
triggers: {
title: 'func triggers',
type: 'object',
Expand Down
93 changes: 34 additions & 59 deletions packages/common/src/task/build-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@
import { Task } from './task';
import { ContainerImage } from '../types';
import { StackContainer } from '../stack';
import { dockerodeEvtToString } from '../index';
// import rimraf from 'rimraf';

import tar from 'tar-fs';
import Docker from 'dockerode';
import { oneLine } from 'common-tags';
import execa from 'execa';

interface BuildContainerTaskOptions {
baseDir: string;
Expand All @@ -29,74 +26,52 @@ interface BuildContainerTaskOptions {

export class BuildContainerTask extends Task<ContainerImage> {
private container: StackContainer;
// private readonly stack: Stack;
private readonly provider: string;

constructor({ container, provider = 'local' }: BuildContainerTaskOptions) {
super(`${container.getName()}`);
this.container = container;
// this.stack = stack;
this.provider = provider;
}

async do(): Promise<ContainerImage> {
const docker = new Docker();

// const ignoreFiles = await Template.getDockerIgnoreFiles(template);

const pack = tar.pack(this.container.getContext(), {
// TODO: support ignore again
// ignore: (name) =>
// // Simple filter before more complex multimatch
// ignoreFiles.filter((f) => name.includes(f)).length > 0 || match(name, ignoreFiles).length > 0,
});
const imageId = this.container.getImageTagName(this.provider);
const { args = {} } = this.container.getDescriptor();

// FIXME: Currently dockerode does not support dockerfiles specified outside of build context
const dockerfile = this.container.getDockerfile();

const options = {
buildargs: {
PROVIDER: this.provider,
},
t: this.container.getImageTagName(this.provider),
dockerfile,
};
const cmd = oneLine`
docker build ${this.container.getContext()}
-f ${this.container.getDockerfile()}
-t ${imageId}
--progress plain
--build-arg PROVIDER=${this.provider}
${Object.keys(args)
.map((k) => `--build-arg ${k}=${args[k]}`)
.join(' ')}
`;

let stream: NodeJS.ReadableStream;
try {
stream = await docker.buildImage(pack, options);
} catch (error) {
if (error.errno && error.errno === -61) {
throw new Error('Unable to connect to docker, is it running locally?');
}
throw error;
}

// Get build updates
const buildResults = await new Promise<any[]>((resolve, reject) => {
docker.modem.followProgress(
stream,
(errorInner: Error, resolveInner: Record<string, any>[]) =>
errorInner ? reject(errorInner) : resolve(resolveInner),
(event: any) => {
try {
this.update(dockerodeEvtToString(event));
} catch (error) {
reject(new Error(error.message.replace(/\n/g, '')));
}
const dockerProcess = execa.command(cmd, {
// Enable buildkit for out of context dockerfile
env: {
DOCKER_BUILDKIT: '1',
},
);
});
});

// Only outputs on stderr
dockerProcess.stderr.on('data', (data) => {
// fs.writeFileSync('debug.txt', data);
this.update(data.toString());
});

const filteredResults = buildResults.filter((obj) => 'aux' in obj && 'ID' in obj['aux']);
if (filteredResults.length > 0) {
const imageId = filteredResults[filteredResults.length - 1]['aux'].ID.split(':').pop() as string;
return { id: imageId, name: this.container.getName() } as ContainerImage;
} else {
const {
errorDetail: { message },
} = buildResults.pop() as any;
throw new Error(message);
// wait for the process to finalize
await dockerProcess;
} catch (e) {
throw new Error(e.message);
}

return {
id: imageId,
name: this.container.getName(),
};
}
}
1 change: 1 addition & 0 deletions packages/common/src/types/compute/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface NitricContainer<Ext> extends NitricComputeUnit<Ext> {
// The path to the Dockerfile to use to build this source
// relative to context
dockerfile: string;
args?: Record<string, string>;
}
2 changes: 1 addition & 1 deletion packages/plugins/azure/src/tasks/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export class Deploy extends Task<void> {

// DEPLOY SERVICES
let deployedAzureApps: NitricComputeAzureAppService[] = [];
if (stack.getFunctions().length > 0) {
if (stack.getFunctions().length > 0 || stack.getContainers().length > 0) {
// deploy a registry for deploying this stacks containers
// TODO: We will want to prefer a pre-existing registry, supplied by the user
const registry = new containerregistry.Registry(`${stack.getName()}-registry`, {
Expand Down

0 comments on commit 4b3ad01

Please sign in to comment.