Skip to content

Commit

Permalink
feat: support adding in memory files to layers (#15)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: image.addFiles obj keys and values flipped

image.addFiles(obj)
when paths are specified as an object the keys are now `targetDirectory`.
this enables copying the same files into a container to different paths and CustomFiles

* feat: custom files can be added to packs
  • Loading branch information
soldair committed Apr 16, 2019
1 parent e65682b commit 9b716ee
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 70 deletions.
47 changes: 43 additions & 4 deletions README.md
Expand Up @@ -15,7 +15,6 @@ You can do most things with the `Image` class. It takes care of tedious tasks li
Create a new image based off of the official node image with your files in it.

```js
import {Image} from 'container-image-builder'

;(async()=>{
const image = new Image('node:lts-slim','gcr.io/my-project/my-image')
Expand Down Expand Up @@ -94,7 +93,7 @@ Defined in the order that they compose into an "Image":
## API

- `const {Image} = require('container-image-builder')`
- or `import {Image} from 'container-image-builder'` in typescript etc.
- or `import {Image} from 'container-image-builder'` in typescript etc.

### Image builder API

Expand All @@ -104,7 +103,8 @@ Defined in the order that they compose into an "Image":
- targetImage
the name of the image you're going to be saving to. calls to image.save() will replace this image.

- `image.addFiles({[localDirectory]:targetDirectory},options): Promise<..>`
- `image.addFiles({[targetDirectory]:localDirectory},options): Promise<..>`

- tar local directories and place each at targetDirectory in a single layer.
- symlinks are replaced with their files/directories as they are written to the layer tarball by default.
- options
Expand All @@ -120,6 +120,11 @@ Defined in the order that they compose into an "Image":
- `image.addFiles(localDirectory,targetDirectory,options) :Promise<..>`
- `image.addFiles(localDirectory,options) :Promise<..>`

- _BREAKING CHANGE_ between 1x AND 2x
- positions of `targetDirectory` and `localDirectory` were flipped in the object.
- when paths are specified as an object the keys are now `targetDirectory`.
- this enables copying the same files into a container to different paths and CustomFiles

- `image.save(tags?: string[], options)`
- save changes to the image. by default this saves the updated image as the `latest` tag
- `tags`, `string[]`
Expand Down Expand Up @@ -192,6 +197,40 @@ Defined in the order that they compose into an "Image":
- remove the layer tagged in the manifest by digest. save it's offset in the array.
- remove the uncompressedDigest from the image config that matches the offset above

- `const {CustomFile} = require('container-image-builder')`
- you can pass CustomFile to image.addFiles as a localPath to write in memory data or a stream to the layer tarball.
- `image.addFiles({'/help.md':new CustomFile({data:Buffer.from('hello')})})`
- `image.addFiles({'/google.html':new CustomFile({data:request('https://google.com'),size:**you must have size beforehand for streams**})})`
- useful for creating whiteout files etc.

- `customFile = new CustomFile(options)`
- options
- mode
- defaults to `0o644` owner read write, everyone read.
- see [fs.chmod](https://nodejs.org/dist/latest-v10.x/docs/api/fs.html#fs_file_modes)
- permission bits are extracted from the mode provided via `& 0o7777` and type bits are set based on type.
- size
- required if stream. optional with buffer
- data
- a stream or buffer of data which will be the contents of the file.
- required if type is File
- type
- defaults to File
- supported are File, Directory, Symlink
- linkPath
- only used if Symlink

- `customFile.uid`
- default 0. set to number if you want to set uid
- `customFile.gid`
- default 0. set to number if you want to set gid
- `customFile.ctime`
- default new Date(). set to Date if you want to set
- `customFile.atime`
- default new Date(). set to Date if you want to set
- `customFile.mtime`
- default new Date(). set to Date if you want to set

### docker registry auth

`const {auth} = require('container-image-builder')`
Expand Down Expand Up @@ -267,7 +306,7 @@ like adding a new blob directly to the target registry before you call addLayer.
- the sha256 sum of the blob you want to download
- stream, boolean
- default false
- if you'ed like to download to a buffer or resolve to a readable stream.
- if you'd like to download to a buffer or resolve to a readable stream.

- `client.upload(blob, contentLength, digest) Promise<{contentLength: number, digest: string}> `
- note: upload a blob to the registry. you do not need to know the content length and digest before hand. if they're not provided they'll be calculated on the fly and a 2 step upload will be performed. It's more efficient if you know the contentLength and digest beforehand, but if you're streaming it can be more efficient to calculate on the fly.
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -13,6 +13,7 @@
"fix": "gts fix",
"prepare": "npm run compile",
"pretest": "npm run compile",
"pretest-one": "npm run compile",
"posttest": "npm run check",
"version": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md"
},
Expand Down
32 changes: 18 additions & 14 deletions src/index.ts
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.
//
import * as crypto from 'crypto';
import {GoogleAuthOptions} from 'google-auth-library';
import * as retry from 'p-retry';
import * as path from 'path';
import * as zlib from 'zlib';
Expand All @@ -24,7 +25,6 @@ import {ImageLocation, parse as parseSpecifier} from './image-specifier';
import * as packer from './packer';
import {pending, PendingTracker} from './pending';
import {ImageConfig, ManifestV2, RegistryClient} from './registry';
import { GoogleAuthOptions } from 'google-auth-library';

const tar = require('tar');

Expand Down Expand Up @@ -68,11 +68,12 @@ export class Image {
// manifest

constructor(
imageSpecifier: string, targetImage?: string|ImageOptions, options?: ImageOptions) {
imageSpecifier: string, targetImage?: string|ImageOptions,
options?: ImageOptions) {
this.options = options || {};

if(typeof targetImage !== 'string'){
this.options = this.options || targetImage
if (typeof targetImage !== 'string') {
this.options = this.options || targetImage;
targetImage = undefined;
}

Expand Down Expand Up @@ -151,14 +152,14 @@ export class Image {
throw new Error(
'specifying a target directory name when the dir is an object of name:target doesn\'t make sense. try addFiles({dir:target})');
}
dir = {[dir]: targetDir};
dir = {[targetDir]: dir};
} else if (targetDir) {
// options!
options = targetDir;
}

// have to wrap in promise because the tar stream can emit error out of band
const p = new Promise(async (resolve, reject) => {
let p = new Promise(async (resolve, reject) => {
const tarStream = packer.pack(dir, options);

tarStream.on('error', (e: Error) => reject(e));
Expand All @@ -182,7 +183,7 @@ export class Image {
result.digest, uncompressedDigest, result.contentLength));
});

this.pending.track(p);
p = this.pending.track(p);

return p as Promise<{
mediaType: string; digest: string; size: number;
Expand Down Expand Up @@ -253,14 +254,13 @@ export class Image {
Cmd?: string[],
WorkingDir?: string
}) {
tags = tags || ['latest'];

options = options || {};

const targetImage = this.targetImage;
const client = await this.client(targetImage, true);
const imageData = await this.getImageData();

tags = tags || [targetImage.tag || 'latest'];
options = options || {};

await this.syncBaseImage(options);

await Promise.all(this.pending.active());
Expand Down Expand Up @@ -406,7 +406,7 @@ export const auth = async (
try {
if (image.registry.indexOf('gcr.io') > -1) {
return await gcrAuth(
image, scope, options ? options['gcr.io']||{} : {});
image, scope, options ? options['gcr.io'] || {} : {});
} else if (image.registry.indexOf('docker.io') > -1) {
return await dockerAuth(
image, scope, options ? options['docker.io'] : undefined);
Expand All @@ -423,11 +423,15 @@ export const auth = async (
return res;
};

export const pack = packer.pack;

// expose CustomFile to pass in image.addFiles
export const CustomFile = packer.CustomFile;

export interface AuthConfig {
'gcr.io'?:GoogleAuthOptions;
'gcr.io'?: GoogleAuthOptions;
// tslint:disable-next-line:no-any
'docker.io'?:any;
'docker.io'?: any;
// tslint:disable-next-line:no-any
[k: string]: any;
}
Expand Down

0 comments on commit 9b716ee

Please sign in to comment.