feat: class ful docker api client

This commit is contained in:
MiaoWoo 2020-01-03 14:30:39 +08:00
parent 84c304a26a
commit e53f633498
19 changed files with 320 additions and 147 deletions

View File

@ -7,4 +7,13 @@ export declare namespace query {
[key: string]: boolean [key: string]: boolean
} }
} }
}
export interface LogsOpts {
follow?: boolean;
stdout?: boolean;
stderr?: boolean;
since?: number;
until?: number;
timestamps?: boolean;
tail?: number | "all";
} }

View File

@ -6,14 +6,7 @@ export declare namespace container {
limit?: number; limit?: number;
size?: boolean; size?: boolean;
} }
export interface LogsOpts { export interface LogsOpts extends common.LogsOpts {
follow?: boolean;
stdout?: boolean;
stderr?: boolean;
since?: number;
until?: number;
timestamps?: boolean;
tail?: number | "all";
} }
export namespace exec { export namespace exec {
export interface Create { export interface Create {

View File

@ -1,3 +1,4 @@
export * from './task'
export * from './swarm' export * from './swarm'
export * from './common' export * from './common'
export * from './config' export * from './config'

View File

@ -2,16 +2,8 @@ import * as common from './common'
export declare namespace service { export declare namespace service {
export interface FilterOpt { export interface FilterOpt {
} }
export interface LogsOpts { export interface LogsOpts extends common.LogsOpts {
details?: boolean;
follow?: boolean;
stdout?: boolean;
stderr?: boolean;
since?: number;
until?: number;
timestamps?: boolean;
tail?: number | "all";
} }
} }

View File

@ -0,0 +1,9 @@
import * as common from './common'
export declare namespace task {
export interface FilterOpt {
}
export interface LogsOpts extends common.LogsOpts {
}
}

View File

@ -0,0 +1,92 @@
import { ServerResponse } from 'http'
import axios, { AxiosInstance, AxiosRequestConfig, Method, AxiosResponse } from 'axios';
export interface DockerApiClient {
get<T>(path: string, data?: object): Promise<T>;
post<T>(path: string, data?: object): Promise<T>;
del<T>(path: string, data?: object): Promise<T>;
stream<T = ServerResponse>(path: string, data?: object): Promise<T>;
getUri(path: string, data?: object): string;
}
class DefaultDockerApiClient implements DockerApiClient {
private api: AxiosInstance;
constructor(host: string = process.env.DOCKER_HOST) {
const instanceConfig: AxiosRequestConfig = {
headers: {
'Content-Type': 'application/json'
}
}
if (host.startsWith("/")) {
instanceConfig.socketPath = host
} else {
instanceConfig.baseURL = host
}
this.api = axios.create(instanceConfig)
}
async get<T>(path: string, data?: object): Promise<T> {
return await this.handle<T>("GET", path, { params: data });
}
async post<T>(path: string, data?: object): Promise<T> {
return await this.handle<T>("POST", path, { data });
}
async del<T>(path: string, data?: object): Promise<T> {
return await this.handle<T>("DELETE", path, { params: data });
}
async stream<T = ServerResponse>(path: string, data?: object): Promise<T> {
return await this.handle<T>("GET", path, { params: data, responseType: "stream" });
}
getUri(path: string, data?: object): string {
return this.api.getUri({
url: path,
params: data
})
}
async handle<T>(method: Method, path: string, reqConfig?: AxiosRequestConfig): Promise<T> {
let config: AxiosRequestConfig = {
method,
url: path,
...reqConfig
};
let startTime = Date.now();
let response: AxiosResponse;
try {
response = await this.api.request(config);
return response.data as T
} catch (ex) {
if (!ex.response) { throw ex; }
response = ex.response;
if (response.status > 299 && config.responseType == "stream") {
let stream = response.data;
response.data = await new Promise<T>((resolve, reject) => {
let cache = '';
stream.on('data', (chunk: ArrayBuffer) => {
cache += chunk.toString()
})
stream.on('end', () => {
resolve(JSON.parse(cache) as T);
})
})
}
throw new Error(JSON.stringify(response.data));
} finally {
if (response) {
console.log(`========== Docker API Invoke ==========
REQUEST METHOD : ${method}
REQUEST PATH : ${response.request.path}
REQUEST PARAMS : ${config.params ? JSON.stringify(config.params) : ''}
REQUEST BODY : ${config.data ? JSON.stringify(config.data) : ''}
RESPONSE BODY : ${toString.call(response.data.pipe) === "[object Function]" ? '<Stream>' : JSON.stringify(response.data)}
HANDLE TIME : ${Date.now() - startTime}ms
=======================================`);
}
}
}
}
export default new DefaultDockerApiClient();

View File

@ -1,18 +1,20 @@
import * as api from '../utils/api'
import * as opts from '../api/opts' import * as opts from '../api/opts'
import * as types from '../api/types' import * as types from '../api/types'
import * as filterUtil from '../api/opts/filter' import * as filterUtil from '../api/opts/filter'
import { DockerApiClient } from './api';
export namespace config { export class Config {
export async function list(filter?: opts.config.FilterOpt) { constructor(public api: DockerApiClient) {
return await api.get<types.config.Config[]>('/configs', { }
list(filter?: opts.config.FilterOpt) {
return this.api.get<types.config.Config[]>('/configs', {
filters: filterUtil.toJSON(filter) filters: filterUtil.toJSON(filter)
}); });
} }
export async function inspect(id: string) { inspect(id: string) {
return await api.get(`/configs/${id}`) return this.api.get(`/configs/${id}`)
} }
export async function create() { create() {
return await api.post<{}>('/configs/create') return this.api.post<{}>('/configs/create')
} }
} }

View File

@ -1,22 +1,31 @@
import * as api from '../utils/api';
import * as opts from '../api/opts' import * as opts from '../api/opts'
import * as types from '../api/types' import * as types from '../api/types'
import * as http from 'http' import * as http from 'http'
import { DockerApiClient } from './api';
export namespace container { export class Container {
export async function list(filters?: opts.container.ListOpts) { private execClient: Exec;
return await api.get<types.container.Container[]>('/containers/json', filters) constructor(public api: DockerApiClient) {
this.execClient = new Exec(api);
} }
export async function inspect(id: string, query: { size: boolean } = { size: false }) { get exec() {
return await api.get<types.container.ContainerJSON>(`/containers/${id}/json`, query); return this.execClient;
} }
export function prune() { async list(filters?: opts.container.ListOpts) {
return api.post<types.container.ContainerPrune>('/containers/prune'); return await this.api.get<types.container.Container[]>('/containers/json', filters)
} }
export async function logs(id: string, opts: opts.container.LogsOpts = {}): Promise<http.ServerResponse> { async inspect(id: string, query: { size: boolean } = { size: false }) {
return await this.api.get<types.container.ContainerJSON>(`/containers/${id}/json`, query);
}
prune() {
return this.api.post<types.container.ContainerPrune>('/containers/prune');
}
async logs(id: string, opts: opts.container.LogsOpts = {}): Promise<http.ServerResponse> {
let data = { let data = {
follow: true, follow: true,
stdout: true, stdout: true,
@ -24,31 +33,32 @@ export namespace container {
tail: 10, tail: 10,
...opts ...opts
} }
return await api.stream(`/containers/${id}/logs`, data); return await this.api.stream(`/containers/${id}/logs`, data);
}
export namespace exec {
export function create(id: string, opts: opts.container.exec.Create = {}): Promise<types.container.exec.CreateResult> {
let request = {
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
DetachKeys: 'ctrl-d',
Tty: true,
Cmd: '/bin/sh',
...opts
}
request.AttachStderr = true
return api.post<types.container.exec.CreateResult>(`/containers/${id}/exec`, request)
}
export function start(id: string, opts: opts.container.exec.Start = {}) {
return api.post<types.container.exec.StartResult>(`/exec/${id}/start`, opts)
}
export function resize(id: string, opts: opts.container.exec.Resize = {}) {
return api.post<types.container.exec.ResizeResult>(`/exec/${id}/resize`, opts)
}
export function inspect(id: string) {
return api.get<types.container.exec.ExecJson>(`/exec/${id}/json`)
}
} }
} }
class Exec {
constructor(public api: DockerApiClient) { }
create(id: string, opts: opts.container.exec.Create = {}): Promise<types.container.exec.CreateResult> {
let request = {
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
DetachKeys: 'ctrl-d',
Tty: true,
Cmd: '/bin/sh',
...opts
}
request.AttachStderr = true
return this.api.post<types.container.exec.CreateResult>(`/containers/${id}/exec`, request)
}
start(id: string, opts: opts.container.exec.Start = {}) {
return this.api.post<types.container.exec.StartResult>(`/exec/${id}/start`, opts)
}
resize(id: string, opts: opts.container.exec.Resize = {}) {
return this.api.post<types.container.exec.ResizeResult>(`/exec/${id}/resize`, opts)
}
inspect(id: string) {
return this.api.get<types.container.exec.ExecJson>(`/exec/${id}/json`)
}
}

View File

@ -1,8 +1,10 @@
import * as api from '../utils/api'
import * as types from '../api/types' import * as types from '../api/types'
import { DockerApiClient } from './api';
export namespace image { export class Image {
export async function list() { constructor(public api: DockerApiClient) {
return await api.get<types.image.Image[]>('/images/json'); }
list() {
return this.api.get<types.image.Image[]>('/images/json');
} }
} }

View File

@ -1,10 +1,67 @@
export * from './node' import api, { DockerApiClient } from './api'
export * from './task' import { Container } from './container'
export * from './image' import { Service } from './service'
export * from './swarm' import { Node } from './node'
export * from './config' import { Task } from './task'
export * from './volume' import { Image } from './image'
export * from './system' import { Swarm } from './swarm'
export * from './network' import { Config } from './config'
export * from './service' import { Volume } from './volume'
export * from './container' import { System } from './system'
import { Network } from './network'
export class DockerClient {
private containerInstance: Container;
private serviceInstance: Service;
private nodeInstance: Node;
private taskInstance: Task;
private imageInstance: Image;
private swarmInstance: Swarm;
private configInstance: Config;
private volumeInstance: Volume;
private systemInstance: System;
private networkInstance: Network;
constructor(apiClient: DockerApiClient = api) {
this.containerInstance = new Container(apiClient)
this.serviceInstance = new Service(apiClient)
this.nodeInstance = new Node(apiClient)
this.taskInstance = new Task(apiClient)
this.imageInstance = new Image(apiClient)
this.swarmInstance = new Swarm(apiClient)
this.configInstance = new Config(apiClient)
this.volumeInstance = new Volume(apiClient)
this.systemInstance = new System(apiClient)
this.networkInstance = new Network(apiClient)
}
get container() {
return this.containerInstance;
}
get service() {
return this.serviceInstance;
}
get node() {
return this.nodeInstance;
}
get task() {
return this.taskInstance;
}
get image() {
return this.imageInstance;
}
get swarm() {
return this.swarmInstance;
}
get config() {
return this.configInstance;
}
get volume() {
return this.volumeInstance;
}
get system() {
return this.systemInstance;
}
get network() {
return this.networkInstance;
}
}
export default new DockerClient();

View File

@ -1,9 +1,11 @@
import * as api from '../utils/api'
import * as opts from '../api/opts' import * as opts from '../api/opts'
import * as types from '../api/types' import * as types from '../api/types'
import { DockerApiClient } from './api'
export namespace network { export class Network {
export async function list(opts?: opts.network.ListOpts) { constructor(public api: DockerApiClient) {
return await api.get<types.network.NetworkResource[]>('/networks', opts) }
list(opts?: opts.network.ListOpts) {
return this.api.get<types.network.NetworkResource[]>('/networks', opts)
} }
} }

View File

@ -1,8 +1,10 @@
import * as api from '../utils/api'
import * as types from '../api/types' import * as types from '../api/types'
import { DockerApiClient } from './api';
export namespace node { export class Node {
export async function list() { constructor(public api: DockerApiClient) {
return await api.get<types.node.Node[]>('/nodes'); }
list() {
return this.api.get<types.node.Node[]>('/nodes');
} }
} }

View File

@ -1,25 +1,27 @@
import * as api from '../utils/api';
import * as opts from '../api/opts'; import * as opts from '../api/opts';
import * as types from '../api/types'; import * as types from '../api/types';
import * as filterUtil from '../api/opts/filter' import * as filterUtil from '../api/opts/filter'
import * as http from 'http' import * as http from 'http'
import { DockerApiClient } from './api';
export namespace service { export class Service {
export async function list(filter?: opts.service.FilterOpt) { constructor(public api: DockerApiClient) {
return await api.get<types.service.Service[]>('/services', { }
async list(filter?: opts.service.FilterOpt) {
return await this.api.get<types.service.Service[]>('/services', {
filters: filterUtil.toJSON(filter) filters: filterUtil.toJSON(filter)
}); });
} }
export async function create() { async create() {
} }
export async function inspect(id: string, query: { insertDefaults: boolean } = { insertDefaults: false }) { async inspect(id: string, query: { insertDefaults: boolean } = { insertDefaults: false }) {
return await api.get<types.service.Service>(`/services/${id}`, query); return await this.api.get<types.service.Service>(`/services/${id}`, query);
} }
export async function update(id: string, query: { version: number, registryAuthFrom?: string, rollback?: string }, data: any) { async update(id: string, query: { version: number, registryAuthFrom?: string, rollback?: string }, data: any) {
return await api.post<any>(api.getUri(`/services/${id}/update`, query), data) return await this.api.post<any>(this.api.getUri(`/services/${id}/update`, query), data)
} }
export async function logs(id: string, opts: opts.service.LogsOpts = {}): Promise<http.ServerResponse> { async logs(id: string, opts: opts.service.LogsOpts = {}): Promise<http.ServerResponse> {
let data = { let data = {
follow: true, follow: true,
stdout: true, stdout: true,
@ -27,6 +29,6 @@ export namespace service {
tail: 10, tail: 10,
...opts ...opts
} }
return await api.stream(`/services/${id}/logs`, data); return await this.api.stream(`/services/${id}/logs`, data);
} }
} }

View File

@ -1,29 +1,26 @@
import * as api from '../utils/api'
import * as opts from '../api/opts' import * as opts from '../api/opts'
import * as types from '../api/types' import * as types from '../api/types'
import { DockerApiClient } from './api';
export namespace swarm { export class Swarm {
export async function inspect() { constructor(public client: DockerApiClient) {
return await api.get<types.swarm.Info>('/swarm');
} }
inspect() {
export async function init(opts: opts.swarm.InitOpts) { return this.client.get<types.swarm.Info>('/swarm');
return await api.post<string>('/swarm/init', opts);
} }
init(opts: opts.swarm.InitOpts) {
export async function join(opts: opts.swarm.JoinOpts) { return this.client.post<string>('/swarm/init', opts);
return await api.post<string>('/swarm/join', opts);
} }
join(opts: opts.swarm.JoinOpts) {
export async function leave(force: boolean = false) { return this.client.post<string>('/swarm/join', opts);
return await api.post<string>(`/swarm/leave?force=${force}`);
} }
leave(force: boolean = false) {
export async function unlockkey() { return this.client.post<string>(`/swarm/leave?force=${force}`);
return await api.get<string>(`/swarm/unlockkey`);
} }
unlockkey() {
export async function unlock(opts: opts.swarm.UnlockOpts) { return this.client.get<string>(`/swarm/unlockkey`);
return await api.post<string>(`/swarm/unlockkey`, opts); }
unlock(opts: opts.swarm.UnlockOpts) {
return this.client.post<string>(`/swarm/unlockkey`, opts);
} }
} }

View File

@ -1,16 +1,18 @@
import * as api from '../utils/api'
import * as types from '../api/types' import * as types from '../api/types'
import { DockerApiClient } from './api';
export namespace system { export class System {
export async function info() { constructor(public api: DockerApiClient) {
return await api.get<types.system.Info>('/info'); }
info() {
return this.api.get<types.system.Info>('/info');
} }
export async function version() { version() {
return await api.get<types.system.Version>('/version'); return this.api.get<types.system.Version>('/version');
} }
export async function events() { events() {
return await api.stream('/events'); return this.api.stream('/events');
} }
} }

View File

@ -1,8 +1,22 @@
import * as api from '../utils/api' import * as opts from '../api/opts'
import * as types from '../api/types' import * as types from '../api/types'
import * as http from 'http'
import { DockerApiClient } from './api';
export namespace task { export class Task {
export async function list() { constructor(public api: DockerApiClient) {
return await api.get<types.task.Task[]>('/tasks'); }
list() {
return this.api.get<types.task.Task[]>('/tasks');
}
logs(id: string, opts: opts.task.LogsOpts = {}): Promise<http.ServerResponse> {
let data = {
follow: true,
stdout: true,
stderr: true,
tail: 10,
...opts
}
return this.api.stream(`/services/${id}/logs`, data);
} }
} }

View File

@ -1,8 +1,10 @@
import * as api from '../utils/api'
import * as types from '../api/types' import * as types from '../api/types'
import { DockerApiClient } from './api';
export namespace volume { export class Volume {
export async function list() { constructor(public api: DockerApiClient) {
return await api.get<types.volume.VolumeJSON>('/volumes'); }
list() {
return this.api.get<types.volume.VolumeJSON>('/volumes');
} }
} }

View File

@ -1 +1,2 @@
export * from './client' import client from './client'
export default client;

View File

@ -1,5 +1,5 @@
import * as http from 'http' import * as http from 'http'
import axios, { AxiosResponse, AxiosRequestConfig, Method, AxiosInstance } from 'axios' import { AxiosResponse, AxiosRequestConfig, Method, AxiosInstance } from 'axios'
let api: AxiosInstance; let api: AxiosInstance;
@ -66,19 +66,3 @@ HANDLE TIME : ${Date.now() - startTime}ms
} }
} }
} }
function init() {
const instanceConfig: AxiosRequestConfig = {
headers: {
'Content-Type': 'application/json'
}
}
if (process.env.DOCKER_HOST.startsWith("/")) {
instanceConfig.socketPath = process.env.DOCKER_HOST
} else {
instanceConfig.baseURL = process.env.DOCKER_HOST
}
api = axios.create(instanceConfig)
}
init();