Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support adding in memory files to layers #15

Merged
merged 4 commits into from Apr 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.
soldair marked this conversation as resolved.
Show resolved Hide resolved

### 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",
soldair marked this conversation as resolved.
Show resolved Hide resolved
"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