@cloud-cli/vm 1.3.0 → 1.3.2
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.
- package/.github/workflows/cicd.yml +3 -6
- package/dist/index.js +1 -1
- package/package.json +10 -7
- package/src/index.spec.ts +51 -49
- package/src/index.ts +1 -1
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
name: CICD
|
|
2
2
|
on:
|
|
3
|
-
push
|
|
4
|
-
|
|
5
|
-
- main
|
|
3
|
+
- push
|
|
4
|
+
|
|
6
5
|
jobs:
|
|
7
6
|
release:
|
|
8
|
-
uses:
|
|
9
|
-
with:
|
|
10
|
-
branches: main
|
|
7
|
+
uses: cloud-cli/workflows/.github/workflows/npm-build-release.yml@main
|
|
11
8
|
secrets:
|
|
12
9
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
13
10
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ async function ls(options) {
|
|
|
23
23
|
const { name, path = '' } = options;
|
|
24
24
|
const root = await getVolumeMountpoint(name);
|
|
25
25
|
const files = await exec('ls', ['-1', join(root, path)]);
|
|
26
|
-
return files.stdout.trim().split('\n');
|
|
26
|
+
return files.stdout.trim().split('\n').filter(Boolean);
|
|
27
27
|
}
|
|
28
28
|
async function fixPermissions(options) {
|
|
29
29
|
const { name } = options;
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloud-cli/vm",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Docker volume management",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"prettier": "@cloud-cli/prettier-config",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "
|
|
11
|
-
"tdd": "jest --watchAll",
|
|
10
|
+
"test": "vitest",
|
|
12
11
|
"build": "tsc"
|
|
13
12
|
},
|
|
14
13
|
"keywords": [
|
|
@@ -24,11 +23,15 @@
|
|
|
24
23
|
"author": "Darlan Alves <me@darlanalv.es>",
|
|
25
24
|
"license": "MIT",
|
|
26
25
|
"devDependencies": {
|
|
27
|
-
"@cloud-cli/jest-config": "^1.0.0",
|
|
28
26
|
"@cloud-cli/prettier-config": "^1.0.0",
|
|
29
|
-
"@cloud-cli/typescript-config": "^1.0.1"
|
|
27
|
+
"@cloud-cli/typescript-config": "^1.0.1",
|
|
28
|
+
"typescript": "^5.4.5",
|
|
29
|
+
"vitest": "^1.6.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@cloud-cli/exec": "^1.0.1"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@cloud-cli/cli": "^2.0.0"
|
|
33
36
|
}
|
|
34
37
|
}
|
package/src/index.spec.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
exec: jest.fn(),
|
|
3
|
-
};
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
4
2
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const execMocks = vi.hoisted(() => ({
|
|
4
|
+
exec: vi.fn(),
|
|
5
|
+
readFile: vi.fn(),
|
|
6
|
+
}));
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
vi.mock('get-port', () => ({ default: vi.fn().mockReturnValue(1234) }));
|
|
9
|
+
vi.mock('@cloud-cli/exec', () => ({ exec: execMocks.exec }));
|
|
10
|
+
|
|
11
|
+
vi.mock('@cloud-cli/exec', () => ({ exec: execMocks.exec }));
|
|
12
|
+
vi.mock('node:fs/promises', () => ({ readFile: execMocks.readFile }));
|
|
11
13
|
|
|
12
14
|
import vm from './index';
|
|
13
|
-
import * as exec from '@cloud-cli/exec';
|
|
14
|
-
import * as fs from 'node:fs/promises';
|
|
15
15
|
|
|
16
16
|
const inspectOutput = `[{
|
|
17
17
|
"CreatedAt": "2023-03-10T10:15:10Z",
|
|
@@ -27,6 +27,11 @@ const inspectOutput = `[{
|
|
|
27
27
|
}]`;
|
|
28
28
|
|
|
29
29
|
describe('volume manager', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
execMocks.exec.mockReset();
|
|
32
|
+
execMocks.readFile.mockReset();
|
|
33
|
+
});
|
|
34
|
+
|
|
30
35
|
describe('list', () => {
|
|
31
36
|
it('should list the available volumes', async () => {
|
|
32
37
|
const execOutput = {
|
|
@@ -34,15 +39,15 @@ describe('volume manager', () => {
|
|
|
34
39
|
stdout: `test\ntest2\n`,
|
|
35
40
|
};
|
|
36
41
|
|
|
37
|
-
execMocks.exec
|
|
42
|
+
execMocks.exec.mockResolvedValue(execOutput);
|
|
38
43
|
|
|
39
44
|
await expect(vm.list()).resolves.toEqual(['test', 'test2']);
|
|
40
|
-
expect(
|
|
45
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'ls', '--format={{.Name}}']);
|
|
41
46
|
});
|
|
42
47
|
|
|
43
48
|
it('should reject in case of error', async () => {
|
|
44
49
|
const output = { ok: false };
|
|
45
|
-
execMocks.exec
|
|
50
|
+
execMocks.exec.mockResolvedValue(output);
|
|
46
51
|
|
|
47
52
|
await expect(vm.list()).rejects.toThrowError('Unable to list volumes');
|
|
48
53
|
});
|
|
@@ -51,29 +56,29 @@ describe('volume manager', () => {
|
|
|
51
56
|
describe('fixPermissions', () => {
|
|
52
57
|
it('should fix write permissions on volume folder', async () => {
|
|
53
58
|
const outputs = [inspectOutput, ''];
|
|
54
|
-
execMocks.exec
|
|
59
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: outputs.shift() }));
|
|
55
60
|
|
|
56
61
|
await expect(vm.fixPermissions({ name: 'test' })).resolves.toEqual(true);
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
62
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'inspect', 'test']);
|
|
63
|
+
expect(execMocks.exec).toHaveBeenCalledWith('chmod', ['-R', 'a+w', '/var/lib/docker/volumes/test/_data']);
|
|
59
64
|
});
|
|
60
65
|
});
|
|
61
66
|
|
|
62
67
|
describe('ls', () => {
|
|
63
68
|
it('should list files of a volume and path', async () => {
|
|
64
69
|
const outputs = [inspectOutput, 'dir', inspectOutput, 'a.txt\nb.txt'];
|
|
65
|
-
execMocks.exec
|
|
70
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: outputs.shift() }));
|
|
66
71
|
|
|
67
72
|
await expect(vm.ls({ name: 'test' })).resolves.toEqual(['dir']);
|
|
68
73
|
await expect(vm.ls({ name: 'test', path: 'dir' })).resolves.toEqual(['a.txt', 'b.txt']);
|
|
69
74
|
|
|
70
|
-
expect(
|
|
71
|
-
expect(
|
|
72
|
-
expect(
|
|
75
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'inspect', 'test']);
|
|
76
|
+
expect(execMocks.exec).toHaveBeenCalledWith('ls', ['-1', '/var/lib/docker/volumes/test/_data']);
|
|
77
|
+
expect(execMocks.exec).toHaveBeenCalledWith('ls', ['-1', '/var/lib/docker/volumes/test/_data/dir']);
|
|
73
78
|
});
|
|
74
79
|
|
|
75
80
|
it('should throw an error if volume name is invalid', async () => {
|
|
76
|
-
execMocks.exec
|
|
81
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: '[]' }));
|
|
77
82
|
await expect(vm.ls({ name: 'notfound' })).rejects.toThrowError('Volume not found');
|
|
78
83
|
});
|
|
79
84
|
});
|
|
@@ -81,21 +86,21 @@ describe('volume manager', () => {
|
|
|
81
86
|
describe('rm', () => {
|
|
82
87
|
it('should remove files of a volume by path', async () => {
|
|
83
88
|
const outputs = [inspectOutput, ''];
|
|
84
|
-
execMocks.exec
|
|
89
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: outputs.shift() }));
|
|
85
90
|
|
|
86
91
|
await expect(vm.rm({ name: 'test', path: 'file.txt' })).resolves.toEqual(true);
|
|
87
92
|
|
|
88
|
-
expect(
|
|
89
|
-
expect(
|
|
93
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'inspect', 'test']);
|
|
94
|
+
expect(execMocks.exec).toHaveBeenCalledWith('rm', ['-r', '/var/lib/docker/volumes/test/_data/file.txt']);
|
|
90
95
|
});
|
|
91
96
|
|
|
92
97
|
it('should throw an error if volume name is invalid', async () => {
|
|
93
|
-
execMocks.exec
|
|
98
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: '[]' }));
|
|
94
99
|
await expect(vm.rm({ name: 'notfound', path: 'file.txt' })).rejects.toThrowError('Volume not found');
|
|
95
100
|
});
|
|
96
101
|
|
|
97
102
|
it('should throw an error if path is invalid', async () => {
|
|
98
|
-
execMocks.exec
|
|
103
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: inspectOutput }));
|
|
99
104
|
await expect(vm.rm({ name: 'nopath' })).rejects.toThrowError('Path not specified');
|
|
100
105
|
expect(execMocks.exec).not.toHaveBeenCalled();
|
|
101
106
|
});
|
|
@@ -103,22 +108,22 @@ describe('volume manager', () => {
|
|
|
103
108
|
|
|
104
109
|
describe('cat', () => {
|
|
105
110
|
it('should read the content of a file in a volume by path', async () => {
|
|
106
|
-
execMocks.exec
|
|
107
|
-
|
|
111
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: inspectOutput }));
|
|
112
|
+
execMocks.readFile.mockImplementation(() => 'test file');
|
|
108
113
|
|
|
109
114
|
await expect(vm.cat({ name: 'test', path: 'file.txt' })).resolves.toEqual('test file');
|
|
110
115
|
|
|
111
|
-
expect(
|
|
112
|
-
expect(
|
|
116
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'inspect', 'test']);
|
|
117
|
+
expect(execMocks.readFile).toHaveBeenCalledWith('/var/lib/docker/volumes/test/_data/file.txt', 'utf8');
|
|
113
118
|
});
|
|
114
119
|
|
|
115
120
|
it('should throw an error if volume name is invalid', async () => {
|
|
116
|
-
execMocks.exec
|
|
121
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: '[]' }));
|
|
117
122
|
await expect(vm.cat({ name: 'notfound', path: 'file.txt' })).rejects.toThrowError('Volume not found');
|
|
118
123
|
});
|
|
119
124
|
|
|
120
125
|
it('should throw an error if path is invalid', async () => {
|
|
121
|
-
execMocks.exec
|
|
126
|
+
execMocks.exec.mockImplementation(() => ({ ok: true, stdout: inspectOutput }));
|
|
122
127
|
await expect(vm.cat({ name: 'nopath' })).rejects.toThrowError('Path not specified');
|
|
123
128
|
expect(execMocks.exec).not.toHaveBeenCalled();
|
|
124
129
|
});
|
|
@@ -127,7 +132,7 @@ describe('volume manager', () => {
|
|
|
127
132
|
describe('show', () => {
|
|
128
133
|
it('should show details of a volume', async () => {
|
|
129
134
|
const execOutput = { ok: true, stdout: inspectOutput };
|
|
130
|
-
execMocks.exec
|
|
135
|
+
execMocks.exec.mockResolvedValue(execOutput);
|
|
131
136
|
|
|
132
137
|
await expect(vm.show({ name: 'test' })).resolves.toEqual({
|
|
133
138
|
name: 'test',
|
|
@@ -138,18 +143,17 @@ describe('volume manager', () => {
|
|
|
138
143
|
},
|
|
139
144
|
});
|
|
140
145
|
|
|
141
|
-
expect(
|
|
146
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'inspect', 'test']);
|
|
142
147
|
});
|
|
143
148
|
|
|
144
149
|
it('should throw an error if volume name is invalid', async () => {
|
|
145
|
-
execMocks.exec = jest.fn();
|
|
146
150
|
await expect(vm.show({ name: 'In$%valid' })).rejects.toThrowError('Invalid name');
|
|
147
|
-
expect(
|
|
151
|
+
expect(execMocks.exec).not.toHaveBeenCalled();
|
|
148
152
|
});
|
|
149
153
|
|
|
150
154
|
it('should throw error if volume does not exist', async () => {
|
|
151
155
|
const output = { ok: true, stdout: '[]' };
|
|
152
|
-
execMocks.exec
|
|
156
|
+
execMocks.exec.mockResolvedValue(output);
|
|
153
157
|
|
|
154
158
|
await expect(vm.show({ name: 'invalid' })).rejects.toThrowError('Volume not found');
|
|
155
159
|
});
|
|
@@ -158,22 +162,21 @@ describe('volume manager', () => {
|
|
|
158
162
|
describe('add', () => {
|
|
159
163
|
it('should add a volume', async () => {
|
|
160
164
|
const execOutput = { ok: true, stdout: 'test' };
|
|
161
|
-
execMocks.exec
|
|
165
|
+
execMocks.exec.mockResolvedValue(execOutput);
|
|
162
166
|
|
|
163
167
|
await expect(vm.add({ name: 'test' })).resolves.toEqual(true);
|
|
164
168
|
|
|
165
|
-
expect(
|
|
169
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'create', 'test']);
|
|
166
170
|
});
|
|
167
171
|
|
|
168
172
|
it('should throw error if volume name is invalid', async () => {
|
|
169
|
-
execMocks.exec = jest.fn();
|
|
170
173
|
await expect(vm.add({ name: 'In$%valid' })).rejects.toThrowError('Invalid name');
|
|
171
|
-
expect(
|
|
174
|
+
expect(execMocks.exec).not.toHaveBeenCalled();
|
|
172
175
|
});
|
|
173
176
|
|
|
174
177
|
it('should throw error if volume creation fails', async () => {
|
|
175
178
|
const output = { ok: false, stdout: '' };
|
|
176
|
-
execMocks.exec
|
|
179
|
+
execMocks.exec.mockResolvedValue(output);
|
|
177
180
|
|
|
178
181
|
await expect(vm.add({ name: 'failed' })).rejects.toThrowError('Unable to create volume');
|
|
179
182
|
});
|
|
@@ -182,22 +185,21 @@ describe('volume manager', () => {
|
|
|
182
185
|
describe('remove', () => {
|
|
183
186
|
it('should add a volume', async () => {
|
|
184
187
|
const execOutput = { ok: true, stdout: '' };
|
|
185
|
-
execMocks.exec
|
|
188
|
+
execMocks.exec.mockResolvedValue(execOutput);
|
|
186
189
|
|
|
187
190
|
await expect(vm.remove({ name: 'test' })).resolves.toEqual(true);
|
|
188
191
|
|
|
189
|
-
expect(
|
|
192
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'rm', 'test']);
|
|
190
193
|
});
|
|
191
194
|
|
|
192
195
|
it('should throw error if volume name is invalid', async () => {
|
|
193
|
-
execMocks.exec = jest.fn();
|
|
194
196
|
await expect(vm.remove({ name: 'In$%valid' })).rejects.toThrowError('Invalid name');
|
|
195
|
-
expect(
|
|
197
|
+
expect(execMocks.exec).not.toHaveBeenCalled();
|
|
196
198
|
});
|
|
197
199
|
|
|
198
200
|
it('should throw error if volume removal fails', async () => {
|
|
199
201
|
const output = { ok: false, stdout: '' };
|
|
200
|
-
execMocks.exec
|
|
202
|
+
execMocks.exec.mockResolvedValue(output);
|
|
201
203
|
|
|
202
204
|
await expect(vm.remove({ name: 'failed' })).rejects.toThrowError('Unable to remove volume');
|
|
203
205
|
});
|
|
@@ -206,11 +208,11 @@ describe('volume manager', () => {
|
|
|
206
208
|
describe('prune', () => {
|
|
207
209
|
it('should remove ununsed volumes', async () => {
|
|
208
210
|
const execOutput = { ok: true, stdout: '' };
|
|
209
|
-
execMocks.exec
|
|
211
|
+
execMocks.exec.mockResolvedValue(execOutput);
|
|
210
212
|
|
|
211
213
|
await expect(vm.prune()).resolves.toEqual('');
|
|
212
214
|
|
|
213
|
-
expect(
|
|
215
|
+
expect(execMocks.exec).toHaveBeenCalledWith('docker', ['volume', 'prune']);
|
|
214
216
|
});
|
|
215
217
|
});
|
|
216
218
|
});
|
package/src/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ async function ls(options: NameAndPathOption) {
|
|
|
41
41
|
const root = await getVolumeMountpoint(name);
|
|
42
42
|
const files = await exec('ls', ['-1', join(root, path)]);
|
|
43
43
|
|
|
44
|
-
return files.stdout.trim().split('\n');
|
|
44
|
+
return files.stdout.trim().split('\n').filter(Boolean);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async function fixPermissions(options: NameOption) {
|