зеркало из https://github.com/docker/node-sdk.git
Add exec example
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
This commit is contained in:
Родитель
713a0371dd
Коммит
7e29dcff15
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
.git
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
10
src/index.ts
10
src/index.ts
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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
821
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче