diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..112db52 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "current file", + "type": "node", + "request": "launch", + "args": [ + "${relativeFile}" + ], + "runtimeArgs": ["-r", "@gitzone/tsrun"], + "cwd": "${workspaceRoot}", + "protocol": "inspector", + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": "test.ts", + "type": "node", + "request": "launch", + "args": [ + "test/test.ts" + ], + "runtimeArgs": ["-r", "@gitzone/tsrun"], + "cwd": "${workspaceRoot}", + "protocol": "inspector", + "internalConsoleOptions": "openOnSessionStart" + } + ] +} diff --git a/README.md b/README.md index 03b89d8..73e2092 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ # @mojoio/docker - unofficial docker engine api abstraction package written in TypeScript ## Availabililty and Links - -- [npmjs.org (npm package)](https://www.npmjs.com/package/@mojoio/docker) -- [gitlab.com (source)](https://gitlab.com/mojoio/docker) -- [github.com (source mirror)](https://github.com/mojoio/docker) -- [docs (typedoc)](https://mojoio.gitlab.io/docker/) +* [npmjs.org (npm package)](https://www.npmjs.com/package/@mojoio/docker) +* [gitlab.com (source)](https://gitlab.com/mojoio/docker) +* [github.com (source mirror)](https://github.com/mojoio/docker) +* [docs (typedoc)](https://mojoio.gitlab.io/docker/) ## Status for master - [![build status](https://gitlab.com/mojoio/docker/badges/master/build.svg)](https://gitlab.com/mojoio/docker/commits/master) [![coverage report](https://gitlab.com/mojoio/docker/badges/master/coverage.svg)](https://gitlab.com/mojoio/docker/commits/master) [![npm downloads per month](https://img.shields.io/npm/dm/@mojoio/docker.svg)](https://www.npmjs.com/package/@mojoio/docker) @@ -36,6 +33,6 @@ const run = async () => { For further information read the linked docs at the top of this readme. > MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh) -> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy) +| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy) [![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com) diff --git a/package.json b/package.json index 17be835..ec7be62 100644 --- a/package.json +++ b/package.json @@ -53,4 +53,4 @@ "npmextra.json", "readme.md" ] -} +} \ No newline at end of file diff --git a/test/test.ts b/test/test.ts index 4a277f2..8aac4b4 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,23 +1,49 @@ import { expect, tap } from '@pushrocks/tapbundle'; -import { DockerHost } from '../ts/index'; +import * as docker from '../ts/index'; -let testDockerHost: DockerHost; +let testDockerHost: docker.DockerHost; tap.test('should create a new Dockersock instance', async () => { - testDockerHost = new DockerHost(); - return expect(testDockerHost).to.be.instanceof(DockerHost); + testDockerHost = new docker.DockerHost(); + return expect(testDockerHost).to.be.instanceof(docker.DockerHost); }); +// Containers tap.test('should list containers', async () => { const containers = await testDockerHost.getContainers(); console.log(containers); }); -tap.skip.test('should pull an image from imagetag', async () => { - // await testDockerHost.pullImage('hosttoday/ht-docker-node:npmci'); +// Networks +tap.test('should list networks', async () => { + const networks = await testDockerHost.getNetworks(); + console.log(networks); }); -tap.test('should return a change Objservable', async tools => { +tap.test('should create a network', async () => { + const newNetwork = await docker.DockerNetwork.createNetwork(testDockerHost, { + Name: 'webgateway' + }); + expect(newNetwork).to.be.instanceOf(docker.DockerNetwork); + expect(newNetwork.Name).to.equal('webgateway'); +}); + +tap.test('should remove a network', async () => { + const webgateway = await docker.DockerNetwork.getNetworkByName(testDockerHost, 'webgateway'); + await webgateway.remove(); +}); + +// Images +tap.test('should pull an image from imagetag', async () => { + const image = await docker.DockerImage.createFromRegistry(testDockerHost, { + imageUrl: 'hosttoday/ht-docker-node', + tag: 'alpine' + }); + expect(image).to.be.instanceOf(docker.DockerImage); + console.log(image); +}); + +tap.test('should return a change Observable', async tools => { const testObservable = await testDockerHost.getEventObservable(); const subscription = testObservable.subscribe(changeObject => { console.log(changeObject); diff --git a/ts/docker.classes.container.ts b/ts/docker.classes.container.ts index 7f7de53..f9bf41c 100644 --- a/ts/docker.classes.container.ts +++ b/ts/docker.classes.container.ts @@ -1,4 +1,4 @@ -import * as plugins from './dockersock.plugins'; +import * as plugins from './docker.plugins'; import * as interfaces from './interfaces'; import { DockerHost } from './docker.classes.host'; @@ -15,7 +15,7 @@ export class DockerContainer { // TODO: Think about getting the config by inpsecting the container for (const containerResult of response.body) { - result.push(new DockerContainer(containerResult)); + result.push(new DockerContainer(dockerHostArg, containerResult)); } return result; } @@ -31,17 +31,33 @@ export class DockerContainer { /** * create a container */ - public static async create(dockerHost: DockerHost, containerCreationDescriptor: interfaces.IContainerCreationDescriptor) { - // TODO implement creating a container + public static async create( + dockerHost: DockerHost, + containerCreationDescriptor: interfaces.IContainerCreationDescriptor + ) { + // check for unique hostname + const existingContainers = await DockerContainer.getContainers(dockerHost); + const sameHostNameContainer = existingContainers.find(container => { + // TODO implement HostName Detection; + return false; + }); + const response = await dockerHost.request('POST', '/containers/create', { + Hostname: containerCreationDescriptor.Hostname, + Domainname: containerCreationDescriptor.Domainname, + User: 'root' + }); + if (response.statusCode < 300) { + plugins.smartlog.defaultLogger.log('info', 'Container created successfully'); + } else { + plugins.smartlog.defaultLogger.log('error', 'There has been a problem when creating the container'); + } } // INSTANCE - constructor(dockerContainerObjectArg: any) { - Object.keys(dockerContainerObjectArg).forEach(keyArg => { - this[keyArg] = dockerContainerObjectArg[keyArg]; - }); - } + // references + public dockerHost: DockerHost; + // properties public Id: string; public Names: string[]; public Image: string; @@ -73,4 +89,10 @@ export class DockerContainer { }; }; public Mounts: any; + constructor(dockerHostArg: DockerHost, dockerContainerObjectArg: any) { + this.dockerHost = dockerHostArg; + Object.keys(dockerContainerObjectArg).forEach(keyArg => { + this[keyArg] = dockerContainerObjectArg[keyArg]; + }); + } } diff --git a/ts/docker.classes.host.ts b/ts/docker.classes.host.ts index 6f82d05..2186479 100644 --- a/ts/docker.classes.host.ts +++ b/ts/docker.classes.host.ts @@ -1,4 +1,4 @@ -import * as plugins from './dockersock.plugins'; +import * as plugins from './docker.plugins'; import { DockerContainer } from './docker.classes.container'; import { DockerNetwork } from './docker.classes.network'; @@ -30,7 +30,7 @@ export class DockerHost { * gets all networks */ public async getNetworks() { - DockerNetwork.getNetworks(this); + return await DockerNetwork.getNetworks(this); } /** @@ -41,6 +41,9 @@ export class DockerHost { return containerArray; } + /** + * + */ public async getEventObservable(): Promise> { const response = await this.requestStreaming('GET', '/events'); return plugins.rxjs.Observable.create(observer => { @@ -59,6 +62,25 @@ export class DockerHost { }); } + /** + * activates docker swarm + */ + public async activateSwarm(addvertisementIpArg: string) { + const response = await this.request('POST', '/swarm/init', { + ListenAddr: '0.0.0.0:2377', + AdvertiseAddr: `${addvertisementIpArg}:2377`, + DataPathPort: 4789, + DefaultAddrPool: ['10.10.0.0/8', '20.20.0.0/8'], + SubnetSize: 24, + ForceNewCluster: false + }); + if (response.statusCode === 200) { + plugins.smartlog.defaultLogger.log('info', 'created Swam succesfully'); + } else { + plugins.smartlog.defaultLogger.log('error', 'could not initiate swarm'); + } + } + /** * fire a request */ @@ -72,6 +94,9 @@ export class DockerHost { }, requestBody: dataArg }); + if (response.statusCode !== 200) { + console.log(response.body); + } return response; } diff --git a/ts/docker.classes.image.ts b/ts/docker.classes.image.ts index fbc38e0..2ba7bff 100644 --- a/ts/docker.classes.image.ts +++ b/ts/docker.classes.image.ts @@ -1,38 +1,104 @@ -import * as plugins from './dockersock.plugins'; +import * as plugins from './docker.plugins'; +import * as interfaces from './interfaces'; import { DockerHost } from './docker.classes.host'; export class DockerImage { // STATIC - public static async createFromRegistry( - dockerHostArg: DockerHost, - dockerImageTag - ): Promise { - const resultingImage = new DockerImage(); - - return resultingImage; + public static async getImages(dockerHost: DockerHost) { + const images: DockerImage[] = []; + const response = await dockerHost.request('GET', '/images/json'); + for (const imageObject of response.body) { + images.push(new DockerImage(dockerHost, imageObject)); + } + return images; } - public static async createFromExistingImage(dockerHostArg: DockerHost, dockerImageTag) {} + public static async createFromRegistry( + dockerHostArg: DockerHost, + creationObject: interfaces.IImageCreationDescriptor + ): Promise { + const response = await dockerHostArg.request( + 'POST', + `/images/create?fromImage=${encodeURIComponent( + creationObject.imageUrl + )}&tag=${encodeURIComponent(creationObject.tag)}` + ); + if (response.statusCode < 300) { + plugins.smartlog.defaultLogger.log( + 'info', + `Successfully pulled image ${creationObject.imageUrl} from the registry` + ); + const image = (await DockerImage.getImages(dockerHostArg)).find(image => true); + return image; + } else { + plugins.smartlog.defaultLogger.log('error', `Failed at the attempt of creating a new image`); + } + } + + public static async tagImageByIdOrName( + dockerHost: DockerHost, + idOrNameArg: string, + newTagArg: string + ) { + const response = await dockerHost.request( + 'POST', + `/images/${encodeURIComponent(idOrNameArg)}/${encodeURIComponent(newTagArg)}` + ); + } + + public static async buildImage(dockerHostArg: DockerHost, dockerImageTag) { + // TODO: implement building an image + } // INSTANCE + // references + public dockerHost: DockerHost; + + // properties /** * the tags for an image */ - public tags: string[] = []; + public tags: string[]; + public Containers: number; + public Created: number; + public Id: string; + public Labels: interfaces.TLabels; + public ParentId: string; + public RepoDigests: string[]; + public RepoTags: string[]; + public SharedSize: number; + public Size: number; + public VirtualSize: number; + + constructor(dockerHostArg, dockerImageObjectArg: any) { + this.dockerHost = dockerHostArg; + Object.keys(dockerImageObjectArg).forEach(keyArg => { + this[keyArg] = dockerImageObjectArg[keyArg]; + }); + } /** * returns a boolean wether the image has a upstream image */ public isUpstreamImage(): boolean { // TODO: implement isUpastreamImage - return true; + return this.RepoTags.length > 0; } /** - * + * pulls the latest version from the registry */ public async pullLatestImageFromRegistry(): Promise { - // TODO: implement pullLatestImageFromRegistry + const dockerImageUrl = this.RepoTags[0].split(':')[0]; + const dockerImageTag = this.RepoTags[0].split(':')[1]; + const updatedImage = await DockerImage.createFromRegistry(this.dockerHost, { + imageUrl: dockerImageUrl, + tag: dockerImageTag + }); + Object.assign(this, updatedImage); + // TODO: Compare image digists before and after return true; } + + } diff --git a/ts/docker.classes.network.ts b/ts/docker.classes.network.ts index 3abfce9..b7c016c 100644 --- a/ts/docker.classes.network.ts +++ b/ts/docker.classes.network.ts @@ -1,4 +1,4 @@ -import * as plugins from './dockersock.plugins'; +import * as plugins from './docker.plugins'; import * as interfaces from './interfaces'; import { DockerHost } from './docker.classes.host'; @@ -6,19 +6,55 @@ import { DockerHost } from './docker.classes.host'; export class DockerNetwork { public static async getNetworks(dockerHost: DockerHost): Promise { const dockerNetworks: DockerNetwork[] = []; + const response = await dockerHost.request('GET', '/networks'); + for (const networkObject of response.body) { + dockerNetworks.push(new DockerNetwork(dockerHost, networkObject)); + } return dockerNetworks; } - public static async createNetwork(dockerHost: DockerHost, networkCreationDescriptor) { - // TODO: implement create network + public static async getNetworkByName(dockerHost: DockerHost, dockerNetworkNameArg: string) { + const networks = await DockerNetwork.getNetworks(dockerHost); + return networks.find(dockerNetwork => dockerNetwork.Name === dockerNetworkNameArg); } - constructor(dockerNetworkObjectArg: any) { - Object.keys(dockerNetworkObjectArg).forEach(keyArg => { - this[keyArg] = dockerNetworkObjectArg[keyArg]; + public static async createNetwork( + dockerHost: DockerHost, + networkCreationDescriptor: interfaces.INetworkCreationDescriptor + ): Promise { + const response = await dockerHost.request('POST', '/networks/create', { + Name: networkCreationDescriptor.Name, + CheckDuplicate: true, + Driver: 'overlay', + EnableIPv6: false, + IPAM: { + Driver: 'default', + Config: [ + { + Subnet: '172.20.10.0/16', + IPRange: '172.20.10.0/24', + Gateway: '172.20.10.11' + } + ] + }, + Internal: true, + Attachable: true, + Ingress: false }); + if (response.statusCode < 300 ) { + plugins.smartlog.defaultLogger.log('info', 'Created network successfully'); + return await DockerNetwork.getNetworkByName(dockerHost, networkCreationDescriptor.Name); + } else { + plugins.smartlog.defaultLogger.log('error', 'There has been an error creating the wanted network'); + return null + } } + // INSTANCE + // references + public dockerHost: DockerHost; + + // properties public Name: string; public Id: string; public Created: string; @@ -29,15 +65,27 @@ export class DockerNetwork { public Attachable: boolean; public Ingress: false; public IPAM: { - Driver: "default" | "bridge" | "overlay", + Driver: 'default' | 'bridge' | 'overlay'; Config: [ { - Subnet: string, - IPRange: string, - Gateway: string + Subnet: string; + IPRange: string; + Gateway: string; } - ] + ]; }; + constructor(dockerHostArg: DockerHost, dockerNetworkObjectArg: any) { + this.dockerHost = dockerHostArg; + Object.keys(dockerNetworkObjectArg).forEach(keyArg => { + this[keyArg] = dockerNetworkObjectArg[keyArg]; + }); + } + /** + * removes the network + */ + public async remove() { + const response = await this.dockerHost.request('DELETE', `/networks/${this.Id}`); + } } diff --git a/ts/docker.classes.service.ts b/ts/docker.classes.service.ts index 6c5a4d6..e1d76b3 100644 --- a/ts/docker.classes.service.ts +++ b/ts/docker.classes.service.ts @@ -1,4 +1,4 @@ -import * as plugins from './dockersock.plugins'; +import * as plugins from './docker.plugins'; import * as interfaces from './interfaces'; import { DockerHost } from './docker.classes.host'; diff --git a/ts/dockersock.plugins.ts b/ts/docker.plugins.ts similarity index 89% rename from ts/dockersock.plugins.ts rename to ts/docker.plugins.ts index f826a0b..50e0d6b 100644 --- a/ts/dockersock.plugins.ts +++ b/ts/docker.plugins.ts @@ -4,6 +4,8 @@ import * as smartlog from '@pushrocks/smartlog'; import * as smartpromise from '@pushrocks/smartpromise'; import * as smartrequest from '@pushrocks/smartrequest'; +smartlog.defaultLogger.enableConsole(); + export { lik, smartlog, smartpromise, smartrequest }; // third party diff --git a/ts/interfaces/container.ts b/ts/interfaces/container.ts index 013978b..aedab5b 100644 --- a/ts/interfaces/container.ts +++ b/ts/interfaces/container.ts @@ -1,7 +1,7 @@ import { DockerNetwork } from '../docker.classes.network'; export interface IContainerCreationDescriptor { - hostname: string; - domainName: string; + Hostname: string; + Domainname: string; networks?: DockerNetwork[]; } diff --git a/ts/interfaces/image.ts b/ts/interfaces/image.ts new file mode 100644 index 0000000..c242b25 --- /dev/null +++ b/ts/interfaces/image.ts @@ -0,0 +1,4 @@ +export interface IImageCreationDescriptor { + imageUrl: string; + tag: string; +} \ No newline at end of file diff --git a/ts/interfaces/index.ts b/ts/interfaces/index.ts index 4d48442..c309b1d 100644 --- a/ts/interfaces/index.ts +++ b/ts/interfaces/index.ts @@ -1,3 +1,5 @@ export * from './container'; +export * from './image'; export * from './label'; +export * from './network'; export * from './port'; diff --git a/ts/interfaces/network.ts b/ts/interfaces/network.ts new file mode 100644 index 0000000..e3c4515 --- /dev/null +++ b/ts/interfaces/network.ts @@ -0,0 +1,6 @@ +/** + * creates a new Network + */ +export interface INetworkCreationDescriptor { + Name: string; +}