@cloud-cli/vm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ name: CICD
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ jobs:
7
+ release:
8
+ uses: homebots/workflows/.github/workflows/node-build-release.yml@master
9
+ with:
10
+ branches: main
11
+ secrets:
12
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
13
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Docker volume management
2
+
3
+ ## Usage
4
+
5
+ ```
6
+ npm i @cloud-cli/vm
7
+ ```
8
+
9
+ ```js
10
+ // cloudy.conf.mjs
11
+
12
+ import volume from '@cloud-cli/vm';
13
+
14
+ export default { volume };
15
+ ```
16
+
17
+ ## API
18
+
19
+ **Create a volume**
20
+
21
+ ```
22
+ cy volume.add --name "foo"
23
+ ```
24
+
25
+ **Remove a volume**
26
+
27
+ ```
28
+ cy volume.remove --name "foo"
29
+ ```
30
+
31
+ **Get details of a volume**
32
+
33
+ ```
34
+ cy volume.show --name "foo"
35
+ ```
36
+
37
+ **List volumes**
38
+
39
+ ```
40
+ cy volume.list
41
+ ```
42
+
43
+ **Prune unused volumes**
44
+
45
+ ```
46
+ cy volume.prune
47
+ ```
@@ -0,0 +1,20 @@
1
+ interface NameOption {
2
+ name: string;
3
+ }
4
+ declare function list(): Promise<string[]>;
5
+ declare function show(options: NameOption): Promise<{
6
+ name: any;
7
+ createdAt: any;
8
+ labels: any;
9
+ }>;
10
+ declare function add(options: NameOption): Promise<string>;
11
+ declare function remove(options: NameOption): Promise<string>;
12
+ declare function prune(): Promise<string>;
13
+ declare const _default: {
14
+ add: typeof add;
15
+ list: typeof list;
16
+ show: typeof show;
17
+ delete: typeof remove;
18
+ prune: typeof prune;
19
+ };
20
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,52 @@
1
+ import { exec } from '@cloud-cli/exec';
2
+ const nameRe = /^[a-z][a-z0-9-]+$/;
3
+ const isValidName = (name) => nameRe.test(name);
4
+ const invalidNameError = new Error('Invalid name');
5
+ async function list() {
6
+ const output = await exec('docker', ['volume', 'ls', '--format="{{.Name}}"']);
7
+ if (!output.ok) {
8
+ throw new Error('Unable to list volumes');
9
+ }
10
+ return output.stdout.trim().split('\n');
11
+ }
12
+ async function show(options) {
13
+ if (!isValidName(options.name)) {
14
+ throw invalidNameError;
15
+ }
16
+ const output = await exec('docker', ['volume', 'inspect', options.name]);
17
+ const list = JSON.parse(output.stdout);
18
+ if (!list.length) {
19
+ throw new Error('Volume not found');
20
+ }
21
+ const { Name, Labels, CreatedAt } = list[0];
22
+ return {
23
+ name: Name,
24
+ createdAt: CreatedAt,
25
+ labels: Labels,
26
+ };
27
+ }
28
+ async function add(options) {
29
+ if (!isValidName(options.name)) {
30
+ throw invalidNameError;
31
+ }
32
+ const output = await exec('docker', ['volume', 'create', options.name]);
33
+ if (output.ok) {
34
+ return '';
35
+ }
36
+ throw new Error('Unable to create volume');
37
+ }
38
+ async function remove(options) {
39
+ if (!isValidName(options.name)) {
40
+ throw invalidNameError;
41
+ }
42
+ const output = await exec('docker', ['volume', 'rm', options.name]);
43
+ if (output.ok) {
44
+ return '';
45
+ }
46
+ throw new Error('Unable to remove volume');
47
+ }
48
+ async function prune() {
49
+ await exec('docker', ['volume', 'prune']);
50
+ return '';
51
+ }
52
+ export default { add, list, show, delete: remove, prune };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@cloud-cli/vm",
3
+ "version": "1.0.0",
4
+ "description": "Docker volume management",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "prettier": "@cloud-cli/prettier-config",
8
+ "scripts": {
9
+ "test": "jest",
10
+ "tdd": "jest --watchAll",
11
+ "build": "tsc"
12
+ },
13
+ "keywords": [
14
+ "cloudy"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/cloud-cli/vm"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "author": "Darlan Alves <me@darlanalv.es>",
24
+ "license": "MIT",
25
+ "devDependencies": {
26
+ "@cloud-cli/jest-config": "^1.0.0",
27
+ "@cloud-cli/prettier-config": "^1.0.0",
28
+ "@cloud-cli/typescript-config": "^1.0.1"
29
+ },
30
+ "dependencies": {
31
+ "@cloud-cli/exec": "^1.0.1"
32
+ }
33
+ }
@@ -0,0 +1,136 @@
1
+ const execMocks = {
2
+ exec: jest.fn(),
3
+ };
4
+
5
+ jest.mock('@cloud-cli/exec', () => {
6
+ return execMocks;
7
+ });
8
+
9
+ import vm from './index';
10
+ import * as exec from '@cloud-cli/exec';
11
+
12
+ const inspectOutput = `[{
13
+ "CreatedAt": "2023-03-10T10:15:10Z",
14
+ "Driver": "local",
15
+ "Labels": {
16
+ "origin": "test/container",
17
+ "version": "534524"
18
+ },
19
+ "Mountpoint": "/var/lib/docker/volumes/test/_data",
20
+ "Name": "test",
21
+ "Options": {},
22
+ "Scope": "local"
23
+ }]`;
24
+
25
+ describe('volume manager', () => {
26
+ describe('list', () => {
27
+ it('should list the available volumes', async () => {
28
+ const execOutput = {
29
+ ok: true,
30
+ stdout: `test\ntest2\n`,
31
+ };
32
+
33
+ execMocks.exec = jest.fn().mockResolvedValue(execOutput);
34
+
35
+ await expect(vm.list()).resolves.toEqual(['test', 'test2']);
36
+ expect(exec.exec).toHaveBeenCalledWith('docker', ['volume', 'ls', '--format="{{.Name}}"']);
37
+ });
38
+
39
+ it('should reject in case of error', async () => {
40
+ const output = { ok: false };
41
+ execMocks.exec = jest.fn().mockResolvedValue(output);
42
+
43
+ await expect(vm.list()).rejects.toThrowError('Unable to list volumes');
44
+ });
45
+ });
46
+
47
+ describe('show', () => {
48
+ it('should show details of a volume', async () => {
49
+ const execOutput = { ok: true, stdout: inspectOutput };
50
+ execMocks.exec = jest.fn().mockResolvedValue(execOutput);
51
+
52
+ await expect(vm.show({ name: 'test' })).resolves.toEqual({
53
+ name: 'test',
54
+ createdAt: '2023-03-10T10:15:10Z',
55
+ labels: {
56
+ origin: 'test/container',
57
+ version: '534524',
58
+ },
59
+ });
60
+
61
+ expect(exec.exec).toHaveBeenCalledWith('docker', ['volume', 'inspect', 'test']);
62
+ });
63
+
64
+ it('should throw error if volume name is invalid', async () => {
65
+ execMocks.exec = jest.fn();
66
+ await expect(vm.show({ name: 'In$%valid' })).rejects.toThrowError('Invalid name');
67
+ expect(exec.exec).not.toHaveBeenCalled();
68
+ });
69
+
70
+ it('should throw error if volume does not exist', async () => {
71
+ const output = { ok: true, stdout: '[]' };
72
+ execMocks.exec = jest.fn().mockResolvedValue(output);
73
+
74
+ await expect(vm.show({ name: 'invalid' })).rejects.toThrowError('Volume not found');
75
+ });
76
+ });
77
+
78
+ describe('add', () => {
79
+ it('should add a volume', async () => {
80
+ const execOutput = { ok: true, stdout: 'test' };
81
+ execMocks.exec = jest.fn().mockResolvedValue(execOutput);
82
+
83
+ await expect(vm.add({ name: 'test' })).resolves.toEqual('');
84
+
85
+ expect(exec.exec).toHaveBeenCalledWith('docker', ['volume', 'create', 'test']);
86
+ });
87
+
88
+ it('should throw error if volume name is invalid', async () => {
89
+ execMocks.exec = jest.fn();
90
+ await expect(vm.add({ name: 'In$%valid' })).rejects.toThrowError('Invalid name');
91
+ expect(exec.exec).not.toHaveBeenCalled();
92
+ });
93
+
94
+ it('should throw error if volume creation fails', async () => {
95
+ const output = { ok: false, stdout: '' };
96
+ execMocks.exec = jest.fn().mockResolvedValue(output);
97
+
98
+ await expect(vm.add({ name: 'failed' })).rejects.toThrowError('Unable to create volume');
99
+ });
100
+ });
101
+
102
+ describe('delete', () => {
103
+ it('should add a volume', async () => {
104
+ const execOutput = { ok: true, stdout: '' };
105
+ execMocks.exec = jest.fn().mockResolvedValue(execOutput);
106
+
107
+ await expect(vm.delete({ name: 'test' })).resolves.toEqual('');
108
+
109
+ expect(exec.exec).toHaveBeenCalledWith('docker', ['volume', 'rm', 'test']);
110
+ });
111
+
112
+ it('should throw error if volume name is invalid', async () => {
113
+ execMocks.exec = jest.fn();
114
+ await expect(vm.delete({ name: 'In$%valid' })).rejects.toThrowError('Invalid name');
115
+ expect(exec.exec).not.toHaveBeenCalled();
116
+ });
117
+
118
+ it('should throw error if volume removal fails', async () => {
119
+ const output = { ok: false, stdout: '' };
120
+ execMocks.exec = jest.fn().mockResolvedValue(output);
121
+
122
+ await expect(vm.delete({ name: 'failed' })).rejects.toThrowError('Unable to remove volume');
123
+ });
124
+ });
125
+
126
+ describe('prune', () => {
127
+ it('should remove ununsed volumes', async () => {
128
+ const execOutput = { ok: true, stdout: '' };
129
+ execMocks.exec = jest.fn().mockResolvedValue(execOutput);
130
+
131
+ await expect(vm.prune()).resolves.toEqual('');
132
+
133
+ expect(exec.exec).toHaveBeenCalledWith('docker', ['volume', 'prune']);
134
+ });
135
+ });
136
+ });
package/src/index.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { exec } from '@cloud-cli/exec';
2
+
3
+ const nameRe = /^[a-z][a-z0-9-]+$/;
4
+ const isValidName = (name) => nameRe.test(name);
5
+ const invalidNameError = new Error('Invalid name');
6
+
7
+ interface NameOption {
8
+ name: string;
9
+ }
10
+
11
+ async function list() {
12
+ const output = await exec('docker', ['volume', 'ls', '--format="{{.Name}}"']);
13
+
14
+ if (!output.ok) {
15
+ throw new Error('Unable to list volumes');
16
+ }
17
+
18
+ return output.stdout.trim().split('\n');
19
+ }
20
+
21
+ async function show(options: NameOption) {
22
+ if (!isValidName(options.name)) {
23
+ throw invalidNameError;
24
+ }
25
+
26
+ const output = await exec('docker', ['volume', 'inspect', options.name]);
27
+ const list = JSON.parse(output.stdout);
28
+
29
+ if (!list.length) {
30
+ throw new Error('Volume not found');
31
+ }
32
+
33
+ const { Name, Labels, CreatedAt } = list[0];
34
+
35
+ return {
36
+ name: Name,
37
+ createdAt: CreatedAt,
38
+ labels: Labels,
39
+ };
40
+ }
41
+
42
+ async function add(options: NameOption) {
43
+ if (!isValidName(options.name)) {
44
+ throw invalidNameError;
45
+ }
46
+
47
+ const output = await exec('docker', ['volume', 'create', options.name]);
48
+
49
+ if (output.ok) {
50
+ return '';
51
+ }
52
+
53
+ throw new Error('Unable to create volume');
54
+ }
55
+
56
+ async function remove(options: NameOption) {
57
+ if (!isValidName(options.name)) {
58
+ throw invalidNameError;
59
+ }
60
+
61
+ const output = await exec('docker', ['volume', 'rm', options.name]);
62
+
63
+ if (output.ok) {
64
+ return '';
65
+ }
66
+
67
+ throw new Error('Unable to remove volume');
68
+ }
69
+
70
+ async function prune() {
71
+ await exec('docker', ['volume', 'prune']);
72
+ return '';
73
+ }
74
+
75
+ export default { add, list, show, delete: remove, prune };