navy 6.0.0 → 7.0.0-alpha.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.
Files changed (149) hide show
  1. package/lib/__tests__/config-provider.js +75 -0
  2. package/lib/__tests__/config.js +130 -0
  3. package/lib/__tests__/driver-logging.js +148 -0
  4. package/lib/__tests__/driver.js +19 -0
  5. package/lib/__tests__/errors.js +49 -0
  6. package/lib/__tests__/http-proxy.js +214 -0
  7. package/lib/__tests__/index.js +25 -0
  8. package/lib/__tests__/service.js +16 -0
  9. package/lib/cli/__tests__/develop.js +239 -0
  10. package/lib/cli/__tests__/external-ip.js +68 -0
  11. package/lib/cli/__tests__/health.js +257 -0
  12. package/lib/cli/__tests__/https.js +210 -0
  13. package/lib/cli/__tests__/import.js +110 -0
  14. package/lib/cli/__tests__/index.js +118 -0
  15. package/lib/cli/__tests__/lan-ip.js +90 -0
  16. package/lib/cli/__tests__/launch.js +179 -0
  17. package/lib/cli/__tests__/live.js +155 -0
  18. package/lib/cli/__tests__/local-ip.js +72 -0
  19. package/lib/cli/__tests__/logs.js +52 -0
  20. package/lib/cli/__tests__/open.js +65 -0
  21. package/lib/cli/__tests__/program.js +472 -0
  22. package/lib/cli/__tests__/ps.js +345 -0
  23. package/lib/cli/__tests__/refresh-config.js +95 -0
  24. package/lib/cli/__tests__/run.js +54 -0
  25. package/lib/cli/__tests__/status.js +204 -0
  26. package/lib/cli/__tests__/updates.js +243 -0
  27. package/lib/cli/__tests__/wait-for-healthy.js +134 -0
  28. package/lib/cli/config/__tests__/index.js +275 -0
  29. package/lib/cli/config/__tests__/wrapper.js +53 -0
  30. package/lib/cli/config/index.js +19 -37
  31. package/lib/cli/config/wrapper.js +0 -6
  32. package/lib/cli/develop.js +7 -21
  33. package/lib/cli/doctor/__tests__/clean-compose-files.js +78 -0
  34. package/lib/cli/doctor/__tests__/index.js +67 -0
  35. package/lib/cli/doctor/__tests__/invalid-compose-config.js +103 -0
  36. package/lib/cli/doctor/__tests__/invalid-state.js +83 -0
  37. package/lib/cli/doctor/__tests__/util.js +91 -0
  38. package/lib/cli/doctor/clean-compose-files.js +5 -13
  39. package/lib/cli/doctor/index.js +0 -12
  40. package/lib/cli/doctor/invalid-compose-config.js +0 -4
  41. package/lib/cli/doctor/invalid-state.js +0 -6
  42. package/lib/cli/doctor/util.js +0 -10
  43. package/lib/cli/external-ip.js +0 -4
  44. package/lib/cli/health.js +0 -12
  45. package/lib/cli/https.js +9 -25
  46. package/lib/cli/import.js +0 -12
  47. package/lib/cli/index.js +0 -9
  48. package/lib/cli/lan-ip.js +2 -8
  49. package/lib/cli/launch.js +0 -9
  50. package/lib/cli/live.js +6 -16
  51. package/lib/cli/local-ip.js +2 -7
  52. package/lib/cli/logs.js +0 -3
  53. package/lib/cli/open.js +2 -7
  54. package/lib/cli/program.js +73 -101
  55. package/lib/cli/ps.js +1 -21
  56. package/lib/cli/refresh-config.js +0 -7
  57. package/lib/cli/run.js +0 -3
  58. package/lib/cli/status.js +0 -11
  59. package/lib/cli/updates.js +0 -22
  60. package/lib/cli/util/__tests__/get-or-initialise-navy.js +66 -0
  61. package/lib/cli/util/__tests__/import.js +123 -0
  62. package/lib/cli/util/__tests__/index.js +17 -0
  63. package/lib/cli/util/__tests__/merge-action-options.js +47 -0
  64. package/lib/cli/util/__tests__/reconfigure.js +78 -0
  65. package/lib/cli/util/get-or-initialise-navy.js +0 -7
  66. package/lib/cli/util/import.js +0 -9
  67. package/lib/cli/util/index.js +0 -2
  68. package/lib/cli/util/merge-action-options.js +20 -0
  69. package/lib/cli/util/reconfigure.js +0 -4
  70. package/lib/cli/wait-for-healthy.js +0 -21
  71. package/lib/client/registry/__tests__/get-credentials.js +62 -0
  72. package/lib/client/registry/__tests__/get-endpoint.js +124 -0
  73. package/lib/client/registry/__tests__/get-fat-manifest.js +67 -0
  74. package/lib/client/registry/__tests__/get-token.js +66 -0
  75. package/lib/client/registry/__tests__/helpers.js +26 -0
  76. package/lib/client/registry/get-credentials.js +3 -9
  77. package/lib/client/registry/get-endpoint.js +29 -63
  78. package/lib/client/registry/get-fat-manifest.js +2 -9
  79. package/lib/client/registry/get-token.js +2 -13
  80. package/lib/client/registry/helpers.js +0 -4
  81. package/lib/config-provider.js +0 -12
  82. package/lib/config-providers/filesystem/__tests__/index.js +176 -0
  83. package/lib/config-providers/filesystem/index.js +5 -23
  84. package/lib/config-providers/npm/__tests__/index.js +226 -0
  85. package/lib/config-providers/npm/__tests__/util.js +1 -2
  86. package/lib/config-providers/npm/index.js +12 -35
  87. package/lib/config-providers/npm/util.js +0 -3
  88. package/lib/config.js +4 -19
  89. package/lib/domain/__tests__/container-image.js +81 -0
  90. package/lib/domain/__tests__/oci-api-specification.js +23 -0
  91. package/lib/domain/container-image.js +8 -21
  92. package/lib/domain/oci-api-specification.js +3 -5
  93. package/lib/driver-logging.js +0 -19
  94. package/lib/driver.js +0 -4
  95. package/lib/drivers/docker-compose/__tests__/client.js +249 -0
  96. package/lib/drivers/docker-compose/__tests__/index.js +430 -0
  97. package/lib/drivers/docker-compose/client.js +0 -16
  98. package/lib/drivers/docker-compose/index.js +7 -49
  99. package/lib/errors.js +0 -10
  100. package/lib/http-proxy.js +28 -23
  101. package/lib/index.js +1 -9
  102. package/lib/middleware/__tests__/add-service-proxy-config.js +258 -0
  103. package/lib/middleware/__tests__/develop.js +120 -0
  104. package/lib/middleware/__tests__/helpers.js +154 -0
  105. package/lib/middleware/__tests__/port-override.js +125 -0
  106. package/lib/middleware/__tests__/set-env-vars.js +94 -0
  107. package/lib/middleware/__tests__/set-image.js +76 -0
  108. package/lib/middleware/__tests__/set-logging-driver.js +94 -0
  109. package/lib/middleware/__tests__/tag-override.js +92 -0
  110. package/lib/middleware/add-service-proxy-config.js +8 -16
  111. package/lib/middleware/develop.js +2 -5
  112. package/lib/middleware/helpers.js +6 -12
  113. package/lib/middleware/port-override.js +5 -8
  114. package/lib/middleware/set-env-vars.js +6 -6
  115. package/lib/middleware/set-image.js +4 -5
  116. package/lib/middleware/set-logging-driver.js +6 -6
  117. package/lib/middleware/tag-override.js +4 -6
  118. package/lib/navy/__tests__/default-middleware.js +40 -0
  119. package/lib/navy/__tests__/index.js +1612 -0
  120. package/lib/navy/__tests__/middleware.js +71 -0
  121. package/lib/navy/__tests__/plugin-interface.js +121 -0
  122. package/lib/navy/__tests__/state.js +103 -0
  123. package/lib/navy/__tests__/util.js +24 -0
  124. package/lib/navy/default-middleware.js +0 -10
  125. package/lib/navy/index.js +83 -138
  126. package/lib/navy/middleware.js +0 -6
  127. package/lib/navy/plugin-interface.js +2 -10
  128. package/lib/navy/state.js +12 -24
  129. package/lib/navy/util.js +0 -1
  130. package/lib/service.js +2 -3
  131. package/lib/util/__tests__/exec-async.js +83 -0
  132. package/lib/util/__tests__/external-ip.js +97 -2
  133. package/lib/util/__tests__/get-lan-ip.js +46 -0
  134. package/lib/util/__tests__/has-update.js +136 -0
  135. package/lib/util/__tests__/https.js +301 -0
  136. package/lib/util/__tests__/navyrc.js +45 -0
  137. package/lib/util/__tests__/service-host.js +63 -5
  138. package/lib/util/__tests__/table.js +44 -0
  139. package/lib/util/docker-client.js +2 -10
  140. package/lib/util/exec-async.js +0 -4
  141. package/lib/util/external-ip.js +8 -12
  142. package/lib/util/fs.js +1 -6
  143. package/lib/util/get-lan-ip.js +0 -5
  144. package/lib/util/has-update.js +2 -14
  145. package/lib/util/https.js +11 -55
  146. package/lib/util/navyrc.js +0 -5
  147. package/lib/util/service-host.js +0 -17
  148. package/lib/util/table.js +0 -6
  149. package/package.json +14 -13
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _chai = require("chai");
5
+ var _sinon = _interopRequireDefault(require("sinon"));
6
+ var _proxyquire = _interopRequireDefault(require("proxyquire"));
7
+ var _errors = require("../../errors");
8
+ /* eslint-env mocha */
9
+
10
+ describe('util/https', function () {
11
+ let sandbox;
12
+ let getConfigStub;
13
+ let getConfigDirStub;
14
+ let DEFAULT_TLS_ROOT_CA_DIR;
15
+ let getNavyStub;
16
+ let navyStub;
17
+ let fsStub;
18
+ let pkiStub;
19
+ let mdStub;
20
+ let httpsModule;
21
+ function loadModule() {
22
+ httpsModule = _proxyquire.default.noCallThru()('../https', {
23
+ '../config': {
24
+ getConfig: getConfigStub,
25
+ getConfigDir: getConfigDirStub,
26
+ DEFAULT_TLS_ROOT_CA_DIR
27
+ },
28
+ '../errors': {
29
+ NavyError: _errors.NavyError
30
+ },
31
+ '../navy': {
32
+ getNavy: getNavyStub
33
+ },
34
+ fs: fsStub,
35
+ 'node-forge': {
36
+ pki: pkiStub,
37
+ md: mdStub
38
+ }
39
+ });
40
+ }
41
+ beforeEach(function () {
42
+ sandbox = _sinon.default.createSandbox();
43
+ getConfigStub = sandbox.stub().returns({});
44
+ getConfigDirStub = sandbox.stub().returns('/cfg');
45
+ DEFAULT_TLS_ROOT_CA_DIR = '/default-ca-dir';
46
+ navyStub = {
47
+ url: sandbox.stub().resolves('https://web.local')
48
+ };
49
+ getNavyStub = sandbox.stub().returns(navyStub);
50
+ fsStub = {
51
+ existsSync: sandbox.stub().returns(true),
52
+ mkdirSync: sandbox.stub(),
53
+ unlinkSync: sandbox.stub(),
54
+ readFileSync: sandbox.stub().returns('--PEM--'),
55
+ writeFileSync: sandbox.stub()
56
+ };
57
+ const certObj = {
58
+ publicKey: null,
59
+ serialNumber: null,
60
+ validity: {
61
+ notBefore: new Date(),
62
+ notAfter: new Date()
63
+ },
64
+ setSubject: sandbox.stub(),
65
+ setIssuer: sandbox.stub(),
66
+ setExtensions: sandbox.stub(),
67
+ sign: sandbox.stub(),
68
+ subject: {
69
+ attributes: []
70
+ }
71
+ };
72
+ pkiStub = {
73
+ rsa: {
74
+ generateKeyPair: sandbox.stub().returns({
75
+ publicKey: 'pub',
76
+ privateKey: 'priv'
77
+ })
78
+ },
79
+ createCertificate: sandbox.stub().returns(certObj),
80
+ privateKeyToPem: sandbox.stub().returns('priv-pem'),
81
+ publicKeyToPem: sandbox.stub().returns('pub-pem'),
82
+ certificateToPem: sandbox.stub().returns('cert-pem'),
83
+ privateKeyFromPem: sandbox.stub().returns('priv-key-obj'),
84
+ certificateFromPem: sandbox.stub().returns({
85
+ subject: {
86
+ attributes: [{
87
+ name: 'commonName',
88
+ value: 'ca'
89
+ }]
90
+ }
91
+ })
92
+ };
93
+ mdStub = {
94
+ sha256: {
95
+ create: sandbox.stub().returns('digest')
96
+ }
97
+ };
98
+ loadModule();
99
+ });
100
+ afterEach(function () {
101
+ sandbox.restore();
102
+ });
103
+ describe('getCertsPath', function () {
104
+ it('should return the certs path when it exists', function () {
105
+ fsStub.existsSync.returns(true);
106
+ const result = httpsModule.getCertsPath();
107
+ (0, _chai.expect)(result).to.contain('tls-certs');
108
+ (0, _chai.expect)(fsStub.mkdirSync.called).to.equal(false);
109
+ });
110
+ it('should return an empty string when path does not exist and create is false', function () {
111
+ fsStub.existsSync.returns(false);
112
+ const result = httpsModule.getCertsPath();
113
+ (0, _chai.expect)(result).to.equal('');
114
+ (0, _chai.expect)(fsStub.mkdirSync.called).to.equal(false);
115
+ });
116
+ it('should create the path when create is true and it does not exist', function () {
117
+ fsStub.existsSync.returns(false);
118
+ const result = httpsModule.getCertsPath(true);
119
+ (0, _chai.expect)(fsStub.mkdirSync.calledOnce).to.equal(true);
120
+ (0, _chai.expect)(fsStub.mkdirSync.firstCall.args[0]).to.contain('tls-certs');
121
+ (0, _chai.expect)(fsStub.mkdirSync.firstCall.args[1]).to.eql({
122
+ recursive: true
123
+ });
124
+ (0, _chai.expect)(result).to.contain('tls-certs');
125
+ });
126
+ });
127
+ describe('removeCert', function () {
128
+ it('should unlink each existing cert/key file', async function () {
129
+ fsStub.existsSync.callsFake(p => p === '/cfg/tls-certs' || p.endsWith('web.local.crt') || p.endsWith('web.local.key'));
130
+ navyStub.url.resolves('https://web.local');
131
+ await httpsModule.removeCert({
132
+ navy: 'env-1',
133
+ disable: 'web'
134
+ });
135
+ (0, _chai.expect)(fsStub.unlinkSync.callCount).to.equal(2);
136
+ const paths = fsStub.unlinkSync.getCalls().map(c => c.args[0]);
137
+ (0, _chai.expect)(paths.some(p => p.endsWith('web.local.crt'))).to.equal(true);
138
+ (0, _chai.expect)(paths.some(p => p.endsWith('web.local.key'))).to.equal(true);
139
+ });
140
+ it('should skip files that do not exist', async function () {
141
+ fsStub.existsSync.callsFake(p => p === '/cfg/tls-certs');
142
+ await httpsModule.removeCert({
143
+ navy: 'env-1',
144
+ disable: 'web'
145
+ });
146
+ (0, _chai.expect)(fsStub.unlinkSync.called).to.equal(false);
147
+ });
148
+ it('should wrap unlink errors in NavyError', async function () {
149
+ fsStub.existsSync.callsFake(p => p === '/cfg/tls-certs' || p.endsWith('.crt') || p.endsWith('.key'));
150
+ fsStub.unlinkSync.throws(new Error('permission denied'));
151
+ let caught;
152
+ try {
153
+ await httpsModule.removeCert({
154
+ navy: 'env-1',
155
+ disable: 'web'
156
+ });
157
+ } catch (err) {
158
+ caught = err;
159
+ }
160
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
161
+ });
162
+ });
163
+ describe('generateRootCa', function () {
164
+ it('should create the root CA dir when missing', async function () {
165
+ fsStub.existsSync.callsFake(p => {
166
+ if (p === '/default-ca-dir') return false;
167
+ return false;
168
+ });
169
+ await httpsModule.generateRootCa();
170
+ (0, _chai.expect)(fsStub.mkdirSync.calledWith('/default-ca-dir')).to.equal(true);
171
+ (0, _chai.expect)(fsStub.writeFileSync.calledThrice).to.equal(true);
172
+ });
173
+ it('should wrap mkdir errors in NavyError', async function () {
174
+ fsStub.existsSync.returns(false);
175
+ fsStub.mkdirSync.throws(new Error('permission denied'));
176
+ let caught;
177
+ try {
178
+ await httpsModule.generateRootCa();
179
+ } catch (err) {
180
+ caught = err;
181
+ }
182
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
183
+ });
184
+ it('should skip generation when CA already exists', async function () {
185
+ fsStub.existsSync.callsFake(p => p.endsWith('ca.crt') || p.endsWith('ca.key') || p === '/default-ca-dir');
186
+ await httpsModule.generateRootCa();
187
+ (0, _chai.expect)(pkiStub.rsa.generateKeyPair.called).to.equal(false);
188
+ (0, _chai.expect)(fsStub.writeFileSync.called).to.equal(false);
189
+ });
190
+ it('should generate, sign, and write CA files when missing', async function () {
191
+ fsStub.existsSync.callsFake(p => p === '/default-ca-dir');
192
+ await httpsModule.generateRootCa();
193
+ (0, _chai.expect)(pkiStub.rsa.generateKeyPair.calledWith(2048)).to.equal(true);
194
+ (0, _chai.expect)(pkiStub.createCertificate.calledOnce).to.equal(true);
195
+ (0, _chai.expect)(fsStub.writeFileSync.calledThrice).to.equal(true);
196
+ const writtenPaths = fsStub.writeFileSync.getCalls().map(c => c.args[0]);
197
+ (0, _chai.expect)(writtenPaths.some(p => p.endsWith('ca.key'))).to.equal(true);
198
+ (0, _chai.expect)(writtenPaths.some(p => p.endsWith('ca.pub.key'))).to.equal(true);
199
+ (0, _chai.expect)(writtenPaths.some(p => p.endsWith('ca.crt'))).to.equal(true);
200
+ });
201
+ it('should use a custom root CA dir when supplied via config', async function () {
202
+ getConfigStub.returns({
203
+ tlsRootCaDir: '/custom-ca'
204
+ });
205
+ fsStub.existsSync.callsFake(p => p === '/custom-ca');
206
+ await httpsModule.generateRootCa();
207
+ const writtenPaths = fsStub.writeFileSync.getCalls().map(c => c.args[0]);
208
+ (0, _chai.expect)(writtenPaths.every(p => p.startsWith('/custom-ca/'))).to.equal(true);
209
+ });
210
+ it('should wrap signing errors in NavyError', async function () {
211
+ fsStub.existsSync.callsFake(p => p === '/default-ca-dir');
212
+ const cert = pkiStub.createCertificate();
213
+ cert.sign = sandbox.stub().throws(new Error('sign failure'));
214
+ pkiStub.createCertificate.returns(cert);
215
+ let caught;
216
+ try {
217
+ await httpsModule.generateRootCa();
218
+ } catch (err) {
219
+ caught = err;
220
+ }
221
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
222
+ });
223
+ });
224
+ describe('createCert', function () {
225
+ it('should skip creation when the cert already exists', async function () {
226
+ fsStub.existsSync.callsFake(p => p === '/cfg/tls-certs' || p === '/cfg/tls-certs/web.local.crt');
227
+ await httpsModule.createCert({
228
+ serviceUrl: 'https://web.local'
229
+ });
230
+ (0, _chai.expect)(fsStub.writeFileSync.called).to.equal(false);
231
+ });
232
+ it('should generate, sign, and write a service cert', async function () {
233
+ fsStub.existsSync.callsFake(p => {
234
+ if (p === '/cfg/tls-certs') return true;
235
+ if (p === '/default-ca-dir') return true;
236
+ if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
237
+ return false;
238
+ });
239
+ await httpsModule.createCert({
240
+ serviceUrl: 'https://web.local'
241
+ });
242
+ (0, _chai.expect)(pkiStub.privateKeyFromPem.calledOnce).to.equal(true);
243
+ (0, _chai.expect)(pkiStub.certificateFromPem.calledOnce).to.equal(true);
244
+ (0, _chai.expect)(fsStub.writeFileSync.calledTwice).to.equal(true);
245
+ const writtenPaths = fsStub.writeFileSync.getCalls().map(c => c.args[0]);
246
+ (0, _chai.expect)(writtenPaths.some(p => p.endsWith('web.local.key'))).to.equal(true);
247
+ (0, _chai.expect)(writtenPaths.some(p => p.endsWith('web.local.crt'))).to.equal(true);
248
+ });
249
+ it('should use opts.hostName when supplied', async function () {
250
+ fsStub.existsSync.callsFake(p => {
251
+ if (p === '/cfg/tls-certs') return true;
252
+ if (p === '/default-ca-dir') return true;
253
+ if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
254
+ return false;
255
+ });
256
+ await httpsModule.createCert({
257
+ hostName: 'custom-host'
258
+ });
259
+ const writtenPaths = fsStub.writeFileSync.getCalls().map(c => c.args[0]);
260
+ (0, _chai.expect)(writtenPaths.some(p => p.endsWith('custom-host.key'))).to.equal(true);
261
+ (0, _chai.expect)(writtenPaths.some(p => p.endsWith('custom-host.crt'))).to.equal(true);
262
+ });
263
+ it('should use a custom tlsRootCaDir from config', async function () {
264
+ getConfigStub.returns({
265
+ tlsRootCaDir: '/custom-ca'
266
+ });
267
+ fsStub.existsSync.callsFake(p => {
268
+ if (p === '/cfg/tls-certs') return true;
269
+ if (p === '/custom-ca') return true;
270
+ if (p === '/custom-ca/ca.crt' || p === '/custom-ca/ca.key') return true;
271
+ return false;
272
+ });
273
+ await httpsModule.createCert({
274
+ serviceUrl: 'https://web.local'
275
+ });
276
+ const readPaths = fsStub.readFileSync.getCalls().map(c => c.args[0]);
277
+ (0, _chai.expect)(readPaths.some(p => p === '/custom-ca/ca.crt')).to.equal(true);
278
+ (0, _chai.expect)(readPaths.some(p => p === '/custom-ca/ca.key')).to.equal(true);
279
+ });
280
+ it('should wrap signing errors in NavyError', async function () {
281
+ fsStub.existsSync.callsFake(p => {
282
+ if (p === '/cfg/tls-certs') return true;
283
+ if (p === '/default-ca-dir') return true;
284
+ if (p === '/default-ca-dir/ca.crt' || p === '/default-ca-dir/ca.key') return true;
285
+ return false;
286
+ });
287
+ const cert = pkiStub.createCertificate();
288
+ cert.sign = sandbox.stub().throws(new Error('sign fail'));
289
+ pkiStub.createCertificate.returns(cert);
290
+ let caught;
291
+ try {
292
+ await httpsModule.createCert({
293
+ serviceUrl: 'https://web.local'
294
+ });
295
+ } catch (err) {
296
+ caught = err;
297
+ }
298
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
299
+ });
300
+ });
301
+ });
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _chai = require("chai");
5
+ var _sinon = _interopRequireDefault(require("sinon"));
6
+ var _path = _interopRequireDefault(require("path"));
7
+ var _fs = _interopRequireDefault(require("../fs"));
8
+ var _navyrc = _interopRequireDefault(require("../navyrc"));
9
+ /* eslint-env mocha */
10
+
11
+ describe('navyrc', function () {
12
+ describe('getNavyRc', function () {
13
+ let sandbox;
14
+ beforeEach(function () {
15
+ sandbox = _sinon.default.createSandbox();
16
+ });
17
+ afterEach(function () {
18
+ sandbox.restore();
19
+ });
20
+ it('should read and parse the .navyrc JSON file from the given directory', async function () {
21
+ const config = {
22
+ services: ['api', 'web'],
23
+ plugins: ['nodejs']
24
+ };
25
+ const readStub = sandbox.stub(_fs.default, 'readFileAsync').resolves(JSON.stringify(config));
26
+ const result = await (0, _navyrc.default)('/some/dir');
27
+ (0, _chai.expect)(result).to.eql(config);
28
+ (0, _chai.expect)(readStub.calledOnce).to.equal(true);
29
+ (0, _chai.expect)(readStub.firstCall.args[0]).to.equal(_path.default.join('/some/dir', '.navyrc'));
30
+ });
31
+ it('should return null when the file cannot be read', async function () {
32
+ const err = Object.assign(new Error('not found'), {
33
+ code: 'ENOENT'
34
+ });
35
+ sandbox.stub(_fs.default, 'readFileAsync').rejects(err);
36
+ const result = await (0, _navyrc.default)('/missing/dir');
37
+ (0, _chai.expect)(result).to.equal(null);
38
+ });
39
+ it('should return null when the file content is not valid JSON', async function () {
40
+ sandbox.stub(_fs.default, 'readFileAsync').resolves('not json {{{');
41
+ const result = await (0, _navyrc.default)('/dir');
42
+ (0, _chai.expect)(result).to.equal(null);
43
+ });
44
+ });
45
+ });
@@ -1,11 +1,30 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  var _chai = require("chai");
4
-
5
+ var _sinon = _interopRequireDefault(require("sinon"));
6
+ var _fs = _interopRequireDefault(require("fs"));
7
+ var httpsModule = _interopRequireWildcard(require("../https"));
5
8
  var _serviceHost = require("../service-host");
6
-
9
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
7
10
  /* eslint-env mocha */
11
+
8
12
  describe('service-host', function () {
13
+ let sandbox;
14
+ const originalSubdomain = process.env.NAVY_EXTERNAL_SUBDOMAIN;
15
+ beforeEach(function () {
16
+ sandbox = _sinon.default.createSandbox();
17
+ sandbox.stub(httpsModule, 'getCertsPath').returns('/fake/certs');
18
+ sandbox.stub(_fs.default, 'existsSync').returns(false);
19
+ });
20
+ afterEach(function () {
21
+ sandbox.restore();
22
+ if (originalSubdomain === undefined) {
23
+ delete process.env.NAVY_EXTERNAL_SUBDOMAIN;
24
+ } else {
25
+ process.env.NAVY_EXTERNAL_SUBDOMAIN = originalSubdomain;
26
+ }
27
+ });
9
28
  describe('getNIPSubdomain', function () {
10
29
  it('should return an nip.io domain with the correct IP address', async function () {
11
30
  (0, _chai.expect)(await (0, _serviceHost.getNIPSubdomain)('192.168.1.10')).to.equal('192.168.1.10.nip.io');
@@ -13,6 +32,10 @@ describe('service-host', function () {
13
32
  it('should fallback to localhost when a hostname is passed instead of an IP address', async function () {
14
33
  process.env.NAVY_EXTERNAL_IP = 'somehostname';
15
34
  (0, _chai.expect)(await (0, _serviceHost.getNIPSubdomain)('somehostname')).to.equal('127.0.0.1.nip.io');
35
+ delete process.env.NAVY_EXTERNAL_IP;
36
+ });
37
+ it('should treat malformed IPs (out-of-range octets) as invalid', async function () {
38
+ (0, _chai.expect)(await (0, _serviceHost.getNIPSubdomain)('999.0.0.1')).to.equal('127.0.0.1.nip.io');
16
39
  });
17
40
  });
18
41
  describe('createHostForService', function () {
@@ -20,11 +43,20 @@ describe('service-host', function () {
20
43
  (0, _chai.expect)(await (0, _serviceHost.createHostForService)('someservice', 'mynavy', '127.0.0.1')).to.equal('someservice.mynavy.127.0.0.1.nip.io');
21
44
  (0, _chai.expect)(await (0, _serviceHost.createHostForService)('someservice', 'mynavy', '192.168.1.10')).to.equal('someservice.mynavy.192.168.1.10.nip.io');
22
45
  });
46
+ it('should use NAVY_EXTERNAL_SUBDOMAIN when set, in preference to nip.io', async function () {
47
+ process.env.NAVY_EXTERNAL_SUBDOMAIN = 'dev.example.com';
48
+ (0, _chai.expect)(await (0, _serviceHost.createHostForService)('api', 'env', '10.0.0.1')).to.equal('api.env.dev.example.com');
49
+ });
23
50
  });
24
51
  describe('createUrlForService', function () {
25
- it('should return the correct url for a service', async function () {
52
+ it('should return an http URL when no certificate exists', async function () {
26
53
  (0, _chai.expect)(await (0, _serviceHost.createUrlForService)('someservice', 'mynavy', '192.168.99.100')).to.equal('http://someservice.mynavy.192.168.99.100.nip.io');
27
54
  });
55
+ it('should return an https URL when a matching .crt exists in the certs path', async function () {
56
+ _fs.default.existsSync.restore();
57
+ sandbox.stub(_fs.default, 'existsSync').callsFake(filePath => filePath === '/fake/certs/someservice.mynavy.192.168.99.100.nip.io.crt');
58
+ (0, _chai.expect)(await (0, _serviceHost.createUrlForService)('someservice', 'mynavy', '192.168.99.100')).to.equal('https://someservice.mynavy.192.168.99.100.nip.io');
59
+ });
28
60
  });
29
61
  describe('getUrlFromService', function () {
30
62
  it('should extract the host from service ENV config', function () {
@@ -37,14 +69,40 @@ describe('service-host', function () {
37
69
  };
38
70
  (0, _chai.expect)((0, _serviceHost.getUrlFromService)(service)).to.equal('http://myservice.coolnavy.127.0.0.1.nip.io');
39
71
  });
40
- it('should return null when the service object is not valid', function () {
72
+ it('should return https when a certificate exists for the VIRTUAL_HOST', function () {
73
+ _fs.default.existsSync.restore();
74
+ sandbox.stub(_fs.default, 'existsSync').callsFake(filePath => filePath === '/fake/certs/myservice.coolnavy.127.0.0.1.nip.io.crt');
41
75
  const service = {
42
76
  raw: {
43
- Config: {}
77
+ Config: {
78
+ Env: ['OTHER=value', 'VIRTUAL_HOST=myservice.coolnavy.127.0.0.1.nip.io']
79
+ }
80
+ }
81
+ };
82
+ (0, _chai.expect)((0, _serviceHost.getUrlFromService)(service)).to.equal('https://myservice.coolnavy.127.0.0.1.nip.io');
83
+ });
84
+ it('should return null when env contains no VIRTUAL_HOST entry', function () {
85
+ const service = {
86
+ raw: {
87
+ Config: {
88
+ Env: ['FOO=bar', 'BAZ=qux']
89
+ }
44
90
  }
45
91
  };
46
92
  (0, _chai.expect)((0, _serviceHost.getUrlFromService)(service)).to.equal(null);
93
+ });
94
+ it('should return null when the service object is not valid', function () {
95
+ (0, _chai.expect)((0, _serviceHost.getUrlFromService)({
96
+ raw: {
97
+ Config: {}
98
+ }
99
+ })).to.equal(null);
100
+ (0, _chai.expect)((0, _serviceHost.getUrlFromService)({
101
+ raw: {}
102
+ })).to.equal(null);
103
+ (0, _chai.expect)((0, _serviceHost.getUrlFromService)({})).to.equal(null);
47
104
  (0, _chai.expect)((0, _serviceHost.getUrlFromService)(undefined)).to.equal(null);
105
+ (0, _chai.expect)((0, _serviceHost.getUrlFromService)(null)).to.equal(null);
48
106
  });
49
107
  });
50
108
  });
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _chai = require("chai");
5
+ var _stripAnsi = _interopRequireDefault(require("strip-ansi"));
6
+ var _table = _interopRequireDefault(require("../table"));
7
+ /* eslint-env mocha */
8
+
9
+ describe('table', function () {
10
+ it('should return an empty string when given no rows', function () {
11
+ (0, _chai.expect)((0, _table.default)([])).to.equal('');
12
+ });
13
+ it('should render a single-row table padded by 2 spaces', function () {
14
+ const result = (0, _table.default)([['a', 'bb']]);
15
+ (0, _chai.expect)(result).to.equal('a bb ');
16
+ });
17
+ it('should pad each column to the longest value in that column plus 2', function () {
18
+ const result = (0, _table.default)([['name', 'status'], ['api', 'running'], ['web', 'stopped']]);
19
+ const lines = result.split('\n');
20
+ (0, _chai.expect)(lines).to.have.lengthOf(3);
21
+ (0, _chai.expect)(lines[0]).to.equal('name status ');
22
+ (0, _chai.expect)(lines[1]).to.equal('api running ');
23
+ (0, _chai.expect)(lines[2]).to.equal('web stopped ');
24
+ });
25
+ it('should treat null/undefined cells in the header as empty strings when sizing', function () {
26
+ const result = (0, _table.default)([[null, 'b'], ['xx', 'yyy']]);
27
+ const lines = result.split('\n');
28
+ (0, _chai.expect)(lines[0]).to.equal(' b ');
29
+ (0, _chai.expect)(lines[1]).to.equal('xx yyy ');
30
+ });
31
+ it('should only render the cells that exist on each row (shorter rows are not back-padded)', function () {
32
+ const result = (0, _table.default)([['a', 'b'], ['x']]);
33
+ const lines = result.split('\n');
34
+ (0, _chai.expect)(lines[0]).to.equal('a b ');
35
+ (0, _chai.expect)(lines[1]).to.equal('x ');
36
+ });
37
+ it('should size columns by visible (ANSI-stripped) length', function () {
38
+ const ansiCell = '\u001b[31mhello\u001b[0m';
39
+ const result = (0, _table.default)([['col1'], [ansiCell]]);
40
+ const lines = result.split('\n');
41
+ (0, _chai.expect)((0, _stripAnsi.default)(lines[0])).to.equal('col1 ');
42
+ (0, _chai.expect)((0, _stripAnsi.default)(lines[1])).to.equal('hello ');
43
+ });
44
+ });
@@ -1,19 +1,11 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
-
5
4
  Object.defineProperty(exports, "__esModule", {
6
5
  value: true
7
6
  });
8
7
  exports.default = void 0;
9
-
10
8
  var _dockerode = _interopRequireDefault(require("dockerode"));
11
-
12
- var _bluebird = _interopRequireDefault(require("bluebird"));
13
-
14
- const dockerClient = new _dockerode.default({
15
- Promise: _bluebird.default
16
- });
17
- var _default = dockerClient;
18
- exports.default = _default;
9
+ const dockerClient = new _dockerode.default();
10
+ var _default = exports.default = dockerClient;
19
11
  module.exports = exports.default;
@@ -4,11 +4,8 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.execAsync = execAsync;
7
-
8
7
  var _child_process = require("child_process");
9
-
10
8
  const debug = require('debug')('navy:exec');
11
-
12
9
  function execAsync(command, args = [], callback, opts) {
13
10
  return new Promise((resolve, reject) => {
14
11
  const cmd = command + ' ' + args.join(' ');
@@ -22,7 +19,6 @@ function execAsync(command, args = [], callback, opts) {
22
19
  });
23
20
  childProcess.stdout.on('data', line => debug('out: ' + line.toString()));
24
21
  childProcess.stderr.on('data', line => debug('err: ' + line.toString()));
25
-
26
22
  if (callback) {
27
23
  callback(childProcess);
28
24
  }
@@ -1,20 +1,18 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
-
5
4
  Object.defineProperty(exports, "__esModule", {
6
5
  value: true
7
6
  });
8
7
  exports.dnsLookup = dnsLookup;
9
8
  exports.getExternalIP = getExternalIP;
10
-
11
9
  var _dns = _interopRequireDefault(require("dns"));
12
-
13
10
  const IPV4_FAMILY = 4;
14
-
15
11
  async function dnsLookup(hostname) {
16
12
  return await new Promise((resolve, reject) => {
17
- _dns.default.lookup(hostname, null, (err, ip, ipFamily) => {
13
+ _dns.default.lookup(hostname, {
14
+ family: IPV4_FAMILY
15
+ }, (err, ip, ipFamily) => {
18
16
  if (err || ipFamily !== IPV4_FAMILY) {
19
17
  reject(new Error('Failed to lookup hostname "' + hostname + '"'));
20
18
  } else {
@@ -23,34 +21,32 @@ async function dnsLookup(hostname) {
23
21
  });
24
22
  });
25
23
  }
26
-
27
24
  async function getExternalIP(ipInConfig) {
28
- const dockerHost = process.env.DOCKER_HOST; // DEPRECATED use NAVY_EXTERNAL_IP instead
25
+ const dockerHost = process.env.DOCKER_HOST;
29
26
 
27
+ // DEPRECATED use NAVY_EXTERNAL_IP instead
30
28
  if (process.env.NAVY_HOST) {
31
29
  return process.env.NAVY_HOST;
32
30
  }
33
-
34
31
  if (process.env.NAVY_EXTERNAL_IP) {
35
32
  // Custom external IP
36
33
  return process.env.NAVY_EXTERNAL_IP;
37
34
  }
38
-
39
35
  if (ipInConfig) {
40
36
  return await dnsLookup(ipInConfig);
41
37
  }
42
-
43
38
  if (dockerHost && dockerHost.indexOf('tcp://') !== -1) {
44
39
  // OSX with docker-machine, or a remote docker
45
40
  // dockerHost will be formatted like:
46
41
  // tcp://_._._._:_
47
42
  // We only care about the IP address
43
+
48
44
  let ip = dockerHost.substring('tcp://'.length);
49
45
  ip = ip.substring(0, ip.lastIndexOf(':')).trim();
50
46
  ip = await dnsLookup(ip);
51
47
  return ip;
52
- } // No custom docker host, assume localhost
53
-
48
+ }
54
49
 
50
+ // No custom docker host, assume localhost
55
51
  return '127.0.0.1';
56
52
  }
package/lib/util/fs.js CHANGED
@@ -1,15 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
-
5
4
  Object.defineProperty(exports, "__esModule", {
6
5
  value: true
7
6
  });
8
7
  exports.default = void 0;
9
-
10
8
  var _bluebird = _interopRequireDefault(require("bluebird"));
11
-
12
- var _default = _bluebird.default.promisifyAll(require('fs'));
13
-
14
- exports.default = _default;
9
+ var _default = exports.default = _bluebird.default.promisifyAll(require('fs'));
15
10
  module.exports = exports.default;
@@ -1,23 +1,18 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
-
5
4
  Object.defineProperty(exports, "__esModule", {
6
5
  value: true
7
6
  });
8
7
  exports.getLANIP = getLANIP;
9
-
10
8
  var _dns = _interopRequireDefault(require("dns"));
11
-
12
9
  var _os = _interopRequireDefault(require("os"));
13
-
14
10
  async function getLANIP() {
15
11
  return await new Promise((resolve, reject) => {
16
12
  _dns.default.lookup(_os.default.hostname(), null, (err, addr) => {
17
13
  if (err) {
18
14
  return reject(err);
19
15
  }
20
-
21
16
  return resolve(addr);
22
17
  });
23
18
  });