Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
This commit is contained in:
Djordje Lukic 2020-06-11 13:49:41 +02:00
Родитель 713a0371dd
Коммит 7e29dcff15
20 изменённых файлов: 1063 добавлений и 34 удалений

3
.dockerignore Normal file
Просмотреть файл

@ -0,0 +1,3 @@
node_modules
dist
.git

5
.github/workflows/main.yml поставляемый
Просмотреть файл

@ -31,6 +31,11 @@ jobs:
env:
CI: true
- name: Download CLI
env:
DOCKER_GITHUB_TOKEN: ${{ secrets.DOCKER_GITHUB_TOKEN }}
run: yarn download-cli
- name: Test
run: yarn test --ci --coverage --maxWorkers=2
env:

15
Dockerfile Normal file
Просмотреть файл

@ -0,0 +1,15 @@
# syntax = docker/dockerfile:experimental
FROM node:12-alpine as base
ARG DOCKER_GITHUB_TOKEN
ENV DOCKER_GITHUB_TOKEN=${DOCKER_GITHUB_TOKEN}
COPY package.json .
COPY yarn.lock .
RUN --mount=type=cache,target=cache \
yarn install --frozen-lockfile
COPY . .
RUN yarn download-cli && \
chmod +x docker-linux-amd64 && \
yarn test

6
babel.config.js Normal file
Просмотреть файл

@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
};

8
dist/index.d.ts поставляемый
Просмотреть файл

@ -3,14 +3,14 @@ import { ContextsClient } from './protos/contexts/v1/contexts_grpc_pb';
import { ComposeClient } from './protos/compose/v1/compose_grpc_pb';
import { StreamingClient } from './protos/streams/v1/streams_grpc_pb';
export declare class Containers extends ContainersClient {
constructor();
constructor(address?: string);
}
export declare class Contexts extends ContextsClient {
constructor();
constructor(address?: string);
}
export declare class Compose extends ComposeClient {
constructor();
constructor(address?: string);
}
export declare class Streams extends StreamingClient {
constructor();
constructor(address?: string);
}

10
dist/index.js поставляемый
Просмотреть файл

@ -7,27 +7,27 @@ const contexts_grpc_pb_1 = require("./protos/contexts/v1/contexts_grpc_pb");
const compose_grpc_pb_1 = require("./protos/compose/v1/compose_grpc_pb");
const streams_grpc_pb_1 = require("./protos/streams/v1/streams_grpc_pb");
// ~/Library/Containers/com.docker.docker/Data/cli-api.sock
const address = 'unix:///tmp/backend.sock';
const addr = 'unix:///tmp/backend.sock';
class Containers extends containers_grpc_pb_1.ContainersClient {
constructor() {
constructor(address = addr) {
super(address, grpc_js_1.credentials.createInsecure());
}
}
exports.Containers = Containers;
class Contexts extends contexts_grpc_pb_1.ContextsClient {
constructor() {
constructor(address = addr) {
super(address, grpc_js_1.credentials.createInsecure());
}
}
exports.Contexts = Contexts;
class Compose extends compose_grpc_pb_1.ComposeClient {
constructor() {
constructor(address = addr) {
super(address, grpc_js_1.credentials.createInsecure());
}
}
exports.Compose = Compose;
class Streams extends streams_grpc_pb_1.StreamingClient {
constructor() {
constructor(address = addr) {
super(address, grpc_js_1.credentials.createInsecure());
}
}

2
dist/index.js.map поставляемый
Просмотреть файл

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA4C;AAC5C,kFAA6E;AAC7E,4EAAuE;AACvE,yEAAoE;AACpE,yEAAsE;AAEtE,2DAA2D;AAC3D,MAAM,OAAO,GAAG,0BAA0B,CAAC;AAE3C,MAAa,UAAW,SAAQ,qCAAgB;IAC9C;QACE,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,gCAIC;AAED,MAAa,QAAS,SAAQ,iCAAc;IAC1C;QACE,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,4BAIC;AAED,MAAa,OAAQ,SAAQ,+BAAa;IACxC;QACE,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,0BAIC;AAED,MAAa,OAAQ,SAAQ,iCAAe;IAC1C;QACE,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,0BAIC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA4C;AAC5C,kFAA6E;AAC7E,4EAAuE;AACvE,yEAAoE;AACpE,yEAAsE;AAEtE,2DAA2D;AAC3D,MAAM,IAAI,GAAG,0BAA0B,CAAC;AAExC,MAAa,UAAW,SAAQ,qCAAgB;IAC9C,YAAY,UAAkB,IAAI;QAChC,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,gCAIC;AAED,MAAa,QAAS,SAAQ,iCAAc;IAC1C,YAAY,UAAkB,IAAI;QAChC,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,4BAIC;AAED,MAAa,OAAQ,SAAQ,+BAAa;IACxC,YAAY,UAAkB,IAAI;QAChC,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,0BAIC;AAED,MAAa,OAAQ,SAAQ,iCAAe;IAC1C,YAAY,UAAkB,IAAI;QAChC,KAAK,CAAC,OAAO,EAAE,qBAAW,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAJD,0BAIC"}

7
dist/streams.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
import { BytesMessage as PbBytesMessage } from './protos/streams/v1/streams_pb';
import * as google_protobuf_any_pb from 'google-protobuf/google/protobuf/any_pb';
export declare class BytesMessage extends PbBytesMessage {
toAny(): google_protobuf_any_pb.Any;
static fromAny(any: google_protobuf_any_pb.Any): BytesMessage;
name(): string;
}

21
dist/streams.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BytesMessage = void 0;
const tslib_1 = require("tslib");
const streams_pb_1 = require("./protos/streams/v1/streams_pb");
const google_protobuf_any_pb = tslib_1.__importStar(require("google-protobuf/google/protobuf/any_pb"));
class BytesMessage extends streams_pb_1.BytesMessage {
toAny() {
const any = new google_protobuf_any_pb.Any();
any.pack(this.serializeBinary(), this.name());
return any;
}
static fromAny(any) {
return any.unpack(streams_pb_1.BytesMessage.deserializeBinary, 'com.docker.api.protos.streams.v1.BytesMessage');
}
name() {
return 'type.googleapis.com/com.docker.api.protos.streams.v1.BytesMessage';
}
}
exports.BytesMessage = BytesMessage;
//# sourceMappingURL=streams.js.map

1
dist/streams.js.map поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":";;;;AAAA,+DAAgF;AAChF,uGAAiF;AAEjF,MAAa,YAAa,SAAQ,yBAAc;IAC9C,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,sBAAsB,CAAC,GAAG,EAAE,CAAC;QAC7C,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9C,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,GAA+B;QAC5C,OAAQ,GAAG,CAAC,MAAM,CAChB,yBAAc,CAAC,iBAAiB,EAChC,+CAA+C,CACpB,CAAC;IAChC,CAAC;IAED,IAAI;QACF,OAAO,mEAAmE,CAAC;IAC7E,CAAC;CACF;AAjBD,oCAiBC"}

Просмотреть файл

@ -32,3 +32,4 @@ $ npm run example examples/<file>
* [Listing contexts](./context-ls.ts)
* [Listing containers](./ps.ts)
* [Exec interactively into a container](./exec.ts)

54
examples/exec.ts Normal file
Просмотреть файл

@ -0,0 +1,54 @@
import * as grpc from '@grpc/grpc-js';
import * as readline from 'readline';
import { ExecRequest, ExecResponse } from '../src/containers';
import { Containers, Streams } from '../src/index';
import { BytesMessage } from '../src/streams';
const containerId = process.argv[2];
const client = new Containers();
const streamsClient = new Streams();
// We ask for a stream first
const stream = streamsClient.newStream();
stream.on('metadata', (metadata: grpc.Metadata) => {
// the stream id is returned in a gRPC header, we get it
const streamId = metadata.get('id')[0] as string;
// Put the streamId into the exec request in order to
// be able to have an interactive session
const request = new ExecRequest()
.setCommand('/bin/bash')
.setStreamId(streamId)
.setId(containerId)
.setTty(true);
client.exec(request, (err: grpc.ServiceError | null, _: ExecResponse) => {
if (err != null) {
throw err;
}
// The `exec` request finishes once the stream is closed, we can exit now.
process.exit();
});
});
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
// Send each keypress over the stream
process.stdin.on('keypress', (_, key) => {
const mess = new BytesMessage();
const a = new Uint8Array(key.sequence.length);
for (let i = 0; i <= key.sequence.length; i++) {
a[i] = key.sequence.charCodeAt(i);
}
mess.setValue(a);
stream.write(mess.toAny());
});
// Print everything we receive on the stream
stream.on('data', (chunk: any) => {
const m = BytesMessage.fromAny(chunk);
process.stdout.write(m.getValue());
});

Просмотреть файл

@ -18,6 +18,7 @@
"test:watch": "jest --watch",
"lint": "eslint src --ext .ts",
"download-protos": "ts-node scripts/download-protos.ts",
"download-cli": "ts-node scripts/download-cli.ts",
"example": "ts-node"
},
"prettier": {
@ -30,7 +31,12 @@
"author": "Docker Inc.",
"module": "dist/mylib.esm.js",
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/preset-env": "^7.10.2",
"@babel/preset-typescript": "^7.10.1",
"@octokit/request": "^5.4.5",
"@types/google-protobuf": "^3.7.2",
"@types/jest": "^26.0.0",
"@types/node": "^14.0.13",
"@typescript-eslint/eslint-plugin": "^3.1.0",
"@typescript-eslint/parser": "^3.1.0",

61
scripts/download-cli.ts Normal file
Просмотреть файл

@ -0,0 +1,61 @@
import * as fs from 'fs';
import * as path from 'path';
import { Octokit } from '@octokit/rest';
const { request } = require('@octokit/request');
const download = async () => {
const octokit = new Octokit({
auth: process.env.DOCKER_GITHUB_TOKEN,
});
const asdf = new Octokit();
const latestReleases = await octokit.repos.listReleases({
owner: 'docker',
repo: 'api',
page: 1,
per_page: 1,
});
if (latestReleases.data.length !== 1) {
throw new Error('Found more than one release');
}
const latestRelease = latestReleases.data[0];
const linuxAsset = latestRelease.assets.find(
(asset) => asset.name == 'docker-linux-amd64'
);
if (!linuxAsset) {
throw new Error('linux asset not found');
}
const options = asdf.repos.getReleaseAsset.endpoint.merge({
headers: {
Accept: 'application/octet-stream',
},
owner: 'docker',
repo: 'api',
asset_id: linuxAsset.id,
access_token: process.env.DOCKER_GITHUB_TOKEN,
});
const response = await request(options);
const zipPath = linuxAsset.name;
const file = fs.createWriteStream(zipPath);
file.write(Buffer.from(response.data));
file.end();
fs.chmodSync(path.resolve(zipPath), 755);
};
(async function () {
try {
await download();
} catch (e) {
console.log(e);
process.exit(1);
}
})();

Просмотреть файл

@ -9,7 +9,7 @@ const octokit = new Octokit({
const get = async (p: string) => {
try {
const response = await octokit.repos.getContents({
const response = await octokit.repos.getContent({
owner: 'docker',
repo: 'api',
path: p,

Просмотреть файл

@ -5,28 +5,28 @@ import { ComposeClient } from './protos/compose/v1/compose_grpc_pb';
import { StreamingClient } from './protos/streams/v1/streams_grpc_pb';
// ~/Library/Containers/com.docker.docker/Data/cli-api.sock
const address = 'unix:///tmp/backend.sock';
const addr = 'unix:///tmp/backend.sock';
export class Containers extends ContainersClient {
constructor() {
constructor(address: string = addr) {
super(address, credentials.createInsecure());
}
}
export class Contexts extends ContextsClient {
constructor() {
constructor(address: string = addr) {
super(address, credentials.createInsecure());
}
}
export class Compose extends ComposeClient {
constructor() {
constructor(address: string = addr) {
super(address, credentials.createInsecure());
}
}
export class Streams extends StreamingClient {
constructor() {
constructor(address: string = addr) {
super(address, credentials.createInsecure());
}
}

21
src/streams.ts Normal file
Просмотреть файл

@ -0,0 +1,21 @@
import { BytesMessage as PbBytesMessage } from './protos/streams/v1/streams_pb';
import * as google_protobuf_any_pb from 'google-protobuf/google/protobuf/any_pb';
export class BytesMessage extends PbBytesMessage {
toAny(): google_protobuf_any_pb.Any {
const any = new google_protobuf_any_pb.Any();
any.pack(this.serializeBinary(), this.name());
return any;
}
static fromAny(any: google_protobuf_any_pb.Any): BytesMessage {
return (any.unpack(
PbBytesMessage.deserializeBinary,
'com.docker.api.protos.streams.v1.BytesMessage'
) as unknown) as BytesMessage;
}
name(): string {
return 'type.googleapis.com/com.docker.api.protos.streams.v1.BytesMessage';
}
}

Просмотреть файл

@ -1,7 +0,0 @@
import { sum } from '../src';
describe('blah', () => {
it('works', () => {
expect(sum(1, 1)).toEqual(2);
});
});

36
test/ps.test.ts Normal file
Просмотреть файл

@ -0,0 +1,36 @@
const path = require('path');
const { spawn, spawnSync } = require('child_process');
const { ServiceError } = require('@grpc/grpc-js');
const { Containers } = require('../src');
const { ListRequest, ListResponse } = require('../src/containers');
describe('SDK', () => {
let proc;
const cli = path.resolve('docker-linux-amd64');
const address = 'unix:///tmp/test.sock';
beforeAll(() => {
proc = spawn(cli, ['serve', '--address', address]);
spawnSync(cli, ['context', 'create', 'example', 'example']);
spawnSync(cli, ['context', 'use', 'example']);
});
afterAll(() => {
proc.kill('SIGINT');
});
it('can call the backend', (done) => {
setTimeout(() => {
const client = new Containers(address);
client.list(new ListRequest(), (error, response) => {
expect(error).toBeNull();
expect(response.getContainersList().length).toEqual(2);
done();
});
}, 1e3);
});
});

821
yarn.lock

Разница между файлами не показана из-за своего большого размера Загрузить разницу