@govuk-pay/cli 0.0.30 → 0.0.32
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/package.json +4 -2
- package/resources/pay-local/config/localstack/init-aws.sh +70 -0
- package/resources/pay-local/config/postgres/docker-entrypoint-initdb.d/make_payments_databases.sql +26 -0
- package/resources/pay-local/config/service_config.yaml +193 -0
- package/resources/pay-local/config/services/adminusers.env +49 -0
- package/resources/pay-local/config/services/cardid.env +2 -0
- package/resources/pay-local/config/services/connector.env +70 -0
- package/resources/pay-local/config/services/demo-service.env +10 -0
- package/resources/pay-local/config/services/egress/squid.conf +47 -0
- package/resources/pay-local/config/services/frontend.env +12 -0
- package/resources/pay-local/config/services/java_app.env +1 -0
- package/resources/pay-local/config/services/ledger.env +10 -0
- package/resources/pay-local/config/services/products-ui.env +14 -0
- package/resources/pay-local/config/services/products.env +25 -0
- package/resources/pay-local/config/services/publicapi.env +13 -0
- package/resources/pay-local/config/services/publicauth.env +13 -0
- package/resources/pay-local/config/services/selfservice.env +21 -0
- package/resources/pay-local/config/services/ssl/certs/frontend-proxy.crt +18 -0
- package/resources/pay-local/config/services/ssl/certs/products-ui-proxy.crt +20 -0
- package/resources/pay-local/config/services/ssl/certs/publicapi-proxy.crt +18 -0
- package/resources/pay-local/config/services/ssl/certs/selfservice-proxy.crt +20 -0
- package/resources/pay-local/config/services/ssl/certs/stubs-proxy.crt +18 -0
- package/resources/pay-local/config/services/ssl/keys/frontend-proxy.key +28 -0
- package/resources/pay-local/config/services/ssl/keys/products-ui-proxy.key +28 -0
- package/resources/pay-local/config/services/ssl/keys/publicapi-proxy.key +28 -0
- package/resources/pay-local/config/services/ssl/keys/selfservice-proxy.key +28 -0
- package/resources/pay-local/config/services/ssl/keys/stubs-proxy.key +28 -0
- package/resources/pay-local/config/services/ssl/make-selfsigned.sh +2 -0
- package/resources/pay-local/config/services/stubs.env +12 -0
- package/resources/pay-local/config/services/toolbox.env +6 -0
- package/resources/pay-local/config/services/webhooks.env +9 -0
- package/resources/pay-local/templates/docker-compose.hbs +276 -0
- package/resources/usageDetails.txt +1 -0
- package/src/commands/local/app_client/app_client.js +232 -0
- package/src/commands/local/app_client/fetch_wrapper.js +106 -0
- package/src/commands/local/config/default_config_setup.js +13 -0
- package/src/commands/local/config/last_up_record.js +50 -0
- package/src/commands/local/config/pay_local_cluster.js +225 -0
- package/src/commands/local/config/renderer.js +46 -0
- package/src/commands/local/docker_compose_controller.js +24 -0
- package/src/commands/local/subcommands/account.js +51 -0
- package/src/commands/local/subcommands/browse.js +39 -0
- package/src/commands/local/subcommands/db.js +82 -0
- package/src/commands/local/subcommands/down.js +56 -0
- package/src/commands/local/subcommands/nuke.js +28 -0
- package/src/commands/local/subcommands/otp.js +27 -0
- package/src/commands/local/subcommands/payment.js +68 -0
- package/src/commands/local/subcommands/paymentlink.js +41 -0
- package/src/commands/local/subcommands/restart.js +35 -0
- package/src/commands/local/subcommands/token.js +66 -0
- package/src/commands/local/subcommands/up.js +55 -0
- package/src/commands/local/subcommands/url.js +44 -0
- package/src/commands/local/subcommands/user.js +91 -0
- package/src/commands/local.js +136 -0
- package/src/core/commandRouter.js +4 -0
- package/src/util/configs.js +47 -0
- package/src/util/md5.js +16 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
7
|
+
const fetch_wrapper_1 = require("./fetch_wrapper");
|
|
8
|
+
const pay_local_cluster_js_1 = require("../config/pay_local_cluster.js");
|
|
9
|
+
let SERVICES_CONFIG;
|
|
10
|
+
const appClient = {
|
|
11
|
+
async createPaymentLink(apiKey) {
|
|
12
|
+
const paymentLinkData = {
|
|
13
|
+
pay_api_token: apiKey,
|
|
14
|
+
name: 'exampleName',
|
|
15
|
+
price: 100,
|
|
16
|
+
gateway_account_id: '1',
|
|
17
|
+
return_url: 'https://test.test/some_reference',
|
|
18
|
+
service_name: 'aServiceName',
|
|
19
|
+
type: 'ADHOC',
|
|
20
|
+
service_name_path: 'aServiceNamePath',
|
|
21
|
+
product_name_path: node_crypto_1.default.randomBytes(16).toString('hex'),
|
|
22
|
+
reference_enabled: false
|
|
23
|
+
};
|
|
24
|
+
const url = urlFor('products', '/v1/api/products/');
|
|
25
|
+
const result = await (0, fetch_wrapper_1.postRequest)(url, paymentLinkData);
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const body = result.successfulResult?.body;
|
|
31
|
+
if (body === undefined || typeof body === 'string') {
|
|
32
|
+
console.error('The response to a request payment links did not have a body, or the body was not JSON!');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!Object.hasOwn(body, '_links')) {
|
|
36
|
+
console.error('The response to create a payment link did not include an _links property');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const typedBody = body;
|
|
40
|
+
return typedBody._links;
|
|
41
|
+
},
|
|
42
|
+
async createService() {
|
|
43
|
+
const createServiceData = {
|
|
44
|
+
en: 'My service',
|
|
45
|
+
cy: 'Welsh name for My service'
|
|
46
|
+
};
|
|
47
|
+
const url = urlFor('adminusers', '/v1/api/services');
|
|
48
|
+
const result = await (0, fetch_wrapper_1.postRequest)(url, createServiceData);
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const body = result.successfulResult?.body;
|
|
54
|
+
if (body === undefined || typeof body === 'string') {
|
|
55
|
+
console.error('The response to a create a service did not have a body, or the body was not JSON!');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!Object.hasOwn(body, 'external_id')) {
|
|
59
|
+
console.error('The response to create a service did not include an external_id property');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const typedBody = body;
|
|
63
|
+
return typedBody.external_id;
|
|
64
|
+
},
|
|
65
|
+
async createAccountInServiceWithEmailCollectionMode(serviceExternalID, emailCollectionMode) {
|
|
66
|
+
const account = await appClient.createAccount(serviceExternalID);
|
|
67
|
+
if (account === undefined) {
|
|
68
|
+
console.error('Failed to create account');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!await appClient.setEmailCollectionModeOnAccount(account.gateway_account_id, emailCollectionMode)) {
|
|
72
|
+
console.error('Failed to set email collection mode on gateway account');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!await appClient.addGatewayAccountToService(account.gateway_account_id, serviceExternalID)) {
|
|
76
|
+
console.error('Failed to add the gateway account to the service');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
return account;
|
|
80
|
+
},
|
|
81
|
+
async createAccount(serviceID) {
|
|
82
|
+
const createAccountData = {
|
|
83
|
+
payment_provider: 'sandbox',
|
|
84
|
+
service_name: 'My service',
|
|
85
|
+
type: 'test',
|
|
86
|
+
service_id: serviceID
|
|
87
|
+
};
|
|
88
|
+
const url = urlFor('connector', '/v1/api/accounts');
|
|
89
|
+
const result = await (0, fetch_wrapper_1.postRequest)(url, createAccountData);
|
|
90
|
+
if (!result.success) {
|
|
91
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const body = result.successfulResult?.body;
|
|
95
|
+
if (body === undefined || typeof body === 'string') {
|
|
96
|
+
console.error('The response to a create an account did not have a body, or the body was not JSON!');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const typedBody = body;
|
|
100
|
+
return typedBody;
|
|
101
|
+
},
|
|
102
|
+
async setEmailCollectionModeOnAccount(accountID, emailCollectionMode) {
|
|
103
|
+
const patchEmailCollectionModeData = {
|
|
104
|
+
op: 'replace',
|
|
105
|
+
path: 'email_collection_mode',
|
|
106
|
+
value: emailCollectionMode
|
|
107
|
+
};
|
|
108
|
+
const url = urlFor('connector', `/v1/api/accounts/${accountID}`);
|
|
109
|
+
const result = await (0, fetch_wrapper_1.patchRequest)(url, patchEmailCollectionModeData);
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
},
|
|
116
|
+
async addGatewayAccountToService(accountID, serviceID) {
|
|
117
|
+
const patchAddGatewayAccountToServiceData = {
|
|
118
|
+
op: 'add',
|
|
119
|
+
path: 'gateway_account_ids',
|
|
120
|
+
value: [accountID]
|
|
121
|
+
};
|
|
122
|
+
const url = urlFor('adminusers', `/v1/api/services/${serviceID}`);
|
|
123
|
+
const result = await (0, fetch_wrapper_1.patchRequest)(url, patchAddGatewayAccountToServiceData);
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
},
|
|
130
|
+
async createToken(gatewayAccountID) {
|
|
131
|
+
const createTokenData = {
|
|
132
|
+
account_id: gatewayAccountID,
|
|
133
|
+
description: 'my token',
|
|
134
|
+
created_by: 'system generated',
|
|
135
|
+
token_type: 'CARD'
|
|
136
|
+
};
|
|
137
|
+
const url = urlFor('publicauth', '/v1/frontend/auth');
|
|
138
|
+
const result = await (0, fetch_wrapper_1.postRequest)(url, createTokenData);
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const body = result.successfulResult?.body;
|
|
144
|
+
if (body === undefined || typeof body === 'string') {
|
|
145
|
+
console.error('The response to a create a token did not have a body, or the body was not JSON!');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const typedBody = body;
|
|
149
|
+
return typedBody.token;
|
|
150
|
+
},
|
|
151
|
+
async createPayment(apiToken) {
|
|
152
|
+
const createPaymentData = {
|
|
153
|
+
amount: 1000,
|
|
154
|
+
description: 'my payment',
|
|
155
|
+
reference: 'my payment reference',
|
|
156
|
+
return_url: new URL('https://www.payments.service.gov.uk')
|
|
157
|
+
};
|
|
158
|
+
const url = urlFor('publicapi', '/v1/payments');
|
|
159
|
+
const result = await (0, fetch_wrapper_1.postRequest)(url, createPaymentData, apiToken);
|
|
160
|
+
if (!result.success) {
|
|
161
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const body = result.successfulResult?.body;
|
|
165
|
+
if (body === undefined || typeof body === 'string') {
|
|
166
|
+
console.error('The response to a create a payment did not have a body, or the body was not JSON!');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
return body;
|
|
170
|
+
},
|
|
171
|
+
async createUser(gatewayAccountIDs, roleName) {
|
|
172
|
+
const createUserData = {
|
|
173
|
+
gateway_account_ids: gatewayAccountIDs,
|
|
174
|
+
email: `${node_crypto_1.default.randomBytes(16).toString('hex')}@example.com`,
|
|
175
|
+
password: `${node_crypto_1.default.randomBytes(10).toString('hex')}`,
|
|
176
|
+
telephone_number: '01134960000',
|
|
177
|
+
role_name: roleName
|
|
178
|
+
};
|
|
179
|
+
const url = urlFor('adminusers', '/v1/api/users');
|
|
180
|
+
const result = await (0, fetch_wrapper_1.postRequest)(url, createUserData);
|
|
181
|
+
if (!result.success) {
|
|
182
|
+
console.error((0, fetch_wrapper_1.formatErrorMessageForFailedRequest)(result.unsuccessfulResult));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const body = result.successfulResult?.body;
|
|
186
|
+
if (body === undefined || typeof body === 'string') {
|
|
187
|
+
console.error('The response to a create a user did not have a body, or the body was not JSON!');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const typedAugmentedBody = {
|
|
191
|
+
...createUserData,
|
|
192
|
+
...body
|
|
193
|
+
};
|
|
194
|
+
return typedAugmentedBody;
|
|
195
|
+
},
|
|
196
|
+
async isHealthy(serviceName) {
|
|
197
|
+
const url = urlFor(serviceName, '/healthcheck');
|
|
198
|
+
const response = await (0, fetch_wrapper_1.getRequest)(url);
|
|
199
|
+
return response.success;
|
|
200
|
+
},
|
|
201
|
+
async isUnhealthy(serviceName) {
|
|
202
|
+
return !await this.isHealthy(serviceName);
|
|
203
|
+
},
|
|
204
|
+
async isProxyHealthy(serviceName) {
|
|
205
|
+
const proxyUrl = this.externalUrlFor(serviceName, '/healthcheck', true);
|
|
206
|
+
const response = await (0, fetch_wrapper_1.getRequest)(proxyUrl);
|
|
207
|
+
return response.success;
|
|
208
|
+
},
|
|
209
|
+
externalUrlFor(serviceName, resource, proxy) {
|
|
210
|
+
if (SERVICES_CONFIG === undefined) {
|
|
211
|
+
SERVICES_CONFIG = (0, pay_local_cluster_js_1.loadServicesConfig)();
|
|
212
|
+
}
|
|
213
|
+
if (!(serviceName in SERVICES_CONFIG)) {
|
|
214
|
+
throw new Error(`Tried to look up service config for ${serviceName} but it isn't defined in the service_config.yaml file`);
|
|
215
|
+
}
|
|
216
|
+
const proxyPort = SERVICES_CONFIG[serviceName].proxy_port;
|
|
217
|
+
if (proxy && proxyPort !== undefined) {
|
|
218
|
+
return `https://127.0.0.1:${proxyPort}${resource}`;
|
|
219
|
+
}
|
|
220
|
+
return `http://127.0.0.1:${SERVICES_CONFIG[serviceName].port}${resource}`;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
function urlFor(serviceName, resource) {
|
|
224
|
+
if (SERVICES_CONFIG === undefined) {
|
|
225
|
+
SERVICES_CONFIG = (0, pay_local_cluster_js_1.loadServicesConfig)();
|
|
226
|
+
}
|
|
227
|
+
if (!(serviceName in SERVICES_CONFIG)) {
|
|
228
|
+
throw new Error(`Tried to look up service config for ${serviceName} but it isn't defined in the service_config.yaml file`);
|
|
229
|
+
}
|
|
230
|
+
return appClient.externalUrlFor(serviceName, resource, false);
|
|
231
|
+
}
|
|
232
|
+
exports.default = appClient;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatErrorMessageForFailedRequest = exports.patchRequest = exports.postRequest = exports.getRequest = void 0;
|
|
4
|
+
async function getRequest(url, authToken) {
|
|
5
|
+
return await makeRequest(url, { headers: defaultHeaders(authToken) });
|
|
6
|
+
}
|
|
7
|
+
exports.getRequest = getRequest;
|
|
8
|
+
async function postRequest(url, body, authToken) {
|
|
9
|
+
return await makeRequest(url, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
body: JSON.stringify(body),
|
|
12
|
+
headers: defaultHeaders(authToken)
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
exports.postRequest = postRequest;
|
|
16
|
+
async function patchRequest(url, body, authToken) {
|
|
17
|
+
return await makeRequest(url, {
|
|
18
|
+
method: 'PATCH',
|
|
19
|
+
body: JSON.stringify(body),
|
|
20
|
+
headers: defaultHeaders(authToken)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
exports.patchRequest = patchRequest;
|
|
24
|
+
function formatErrorMessageForFailedRequest(unsuccessfulResult) {
|
|
25
|
+
if (unsuccessfulResult === undefined) {
|
|
26
|
+
return 'The result was successful';
|
|
27
|
+
}
|
|
28
|
+
let errorMessage = '';
|
|
29
|
+
if (unsuccessfulResult.error !== null && unsuccessfulResult.error !== undefined) {
|
|
30
|
+
errorMessage = unsuccessfulResult.error.message;
|
|
31
|
+
}
|
|
32
|
+
if (unsuccessfulResult.response === undefined) {
|
|
33
|
+
return `No response was received from the server, the following error was thrown:\n${errorMessage}`;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
if (unsuccessfulResult.error === undefined) {
|
|
37
|
+
return 'The server returned a non-ok HTTP response, no other error was thrown\n' +
|
|
38
|
+
`Response Code: ${unsuccessfulResult.response?.status}\n` +
|
|
39
|
+
`Response Text: ${unsuccessfulResult.response?.statusText}`;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return 'A response was recevied from the server, but an error was also thrown.\n' +
|
|
43
|
+
`Error: ${errorMessage}` +
|
|
44
|
+
`Response Code: ${unsuccessfulResult?.response?.status}\n` +
|
|
45
|
+
`Response Text: ${unsuccessfulResult?.response?.statusText}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.formatErrorMessageForFailedRequest = formatErrorMessageForFailedRequest;
|
|
50
|
+
/**
|
|
51
|
+
* The error handling around requests is absymal, so we'll catch all possible errors and return a result with a
|
|
52
|
+
* concrete type which is easier for clients to understand and deal with, containing the complexity to only this
|
|
53
|
+
* function
|
|
54
|
+
*/
|
|
55
|
+
async function makeRequest(url, requestOptions) {
|
|
56
|
+
let response;
|
|
57
|
+
try {
|
|
58
|
+
response = await fetch(url, requestOptions);
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
unsuccessfulResult: {
|
|
63
|
+
response
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
unsuccessfulResult: {
|
|
72
|
+
error
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const rawBody = await response.text();
|
|
78
|
+
const jsonResponse = response.headers.get('Content-Type') === 'application/json';
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
successfulResult: {
|
|
82
|
+
response,
|
|
83
|
+
body: jsonResponse ? await JSON.parse(rawBody) : rawBody
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
unsuccessfulResult: {
|
|
91
|
+
response,
|
|
92
|
+
error
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function defaultHeaders(authToken) {
|
|
98
|
+
const headers = new Headers({
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
Accept: 'application/json'
|
|
101
|
+
});
|
|
102
|
+
if (authToken !== undefined) {
|
|
103
|
+
headers.set('Authorization', `Bearer ${authToken}`);
|
|
104
|
+
}
|
|
105
|
+
return headers;
|
|
106
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.copyDefaultConfigs = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function copyDefaultConfigs(to) {
|
|
10
|
+
const sourceDir = node_path_1.default.join(__dirname, '..', '..', '..', '..', 'resources', 'pay-local', 'config');
|
|
11
|
+
node_fs_1.default.cpSync(sourceDir, to, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
exports.copyDefaultConfigs = copyDefaultConfigs;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getLastUp = exports.recordUp = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const LAST_UP_FILENAME = 'last-up-DO-NOT-EDIT.yaml';
|
|
10
|
+
/* Records the last 'up' command docker-compose file by creating a symlink to it in the
|
|
11
|
+
* rendered templates path
|
|
12
|
+
*/
|
|
13
|
+
function recordUp(renderedTemplatesPath, cluster) {
|
|
14
|
+
const lastUpRecordPath = node_path_1.default.resolve(node_path_1.default.join(renderedTemplatesPath, LAST_UP_FILENAME));
|
|
15
|
+
const lastUpActualFilename = `${cluster.name}.yaml`;
|
|
16
|
+
node_fs_1.default.rmSync(lastUpRecordPath, { force: true });
|
|
17
|
+
node_fs_1.default.symlinkSync(lastUpActualFilename, lastUpRecordPath);
|
|
18
|
+
}
|
|
19
|
+
exports.recordUp = recordUp;
|
|
20
|
+
/* Gets the path of the most recently used docker-compose file.
|
|
21
|
+
*
|
|
22
|
+
* Returns undefined if one can't be found, otherwise returns the full path
|
|
23
|
+
* to the yaml file which was last generated by the 'up' command
|
|
24
|
+
*/
|
|
25
|
+
function getLastUp(renderedTemplatesPath) {
|
|
26
|
+
const lastUpRecordPath = node_path_1.default.resolve(node_path_1.default.join(renderedTemplatesPath, LAST_UP_FILENAME));
|
|
27
|
+
const pathStat = node_fs_1.default.lstatSync(lastUpRecordPath, { throwIfNoEntry: false });
|
|
28
|
+
if (pathStat === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!pathStat.isSymbolicLink()) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const symlinkTarget = node_fs_1.default.readlinkSync(lastUpRecordPath);
|
|
35
|
+
if (symlinkTarget === undefined) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!symlinkTarget.endsWith('.yaml')) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const lastUpActualPath = node_path_1.default.resolve(node_path_1.default.join(renderedTemplatesPath, symlinkTarget));
|
|
42
|
+
const lastUpActualPathStat = node_fs_1.default.statSync(lastUpActualPath, { throwIfNoEntry: false });
|
|
43
|
+
if (lastUpActualPathStat === undefined) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (lastUpActualPathStat.isFile()) {
|
|
47
|
+
return lastUpActualPath;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.getLastUp = getLastUp;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadServicesConfig = exports.loadClusterConfig = exports.PayLocalCluster = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
10
|
+
const md5_js_1 = require("../../../util/md5.js");
|
|
11
|
+
// This is the complete cluster configuration that will be used as the context for template rendering
|
|
12
|
+
class PayLocalCluster {
|
|
13
|
+
name;
|
|
14
|
+
payServices;
|
|
15
|
+
dbServices;
|
|
16
|
+
reverseProxyServices;
|
|
17
|
+
egressProxy;
|
|
18
|
+
javaApps;
|
|
19
|
+
nodeApps;
|
|
20
|
+
localJavaApps;
|
|
21
|
+
localNodeApps;
|
|
22
|
+
remoteJavaApps;
|
|
23
|
+
remoteNodeApps;
|
|
24
|
+
anyJavaApps;
|
|
25
|
+
anyNodeApps;
|
|
26
|
+
workspace;
|
|
27
|
+
requiresLocalStack;
|
|
28
|
+
requiresRedis;
|
|
29
|
+
requiresEgressProxy;
|
|
30
|
+
serviceURLEnvVars;
|
|
31
|
+
mountLocalNodeApps;
|
|
32
|
+
noProxyEnvVar;
|
|
33
|
+
defaultServiceConfigsPath;
|
|
34
|
+
environmentOverridesPath;
|
|
35
|
+
constructor(clusterConfig) {
|
|
36
|
+
this.name = clusterConfig.name;
|
|
37
|
+
this.workspace = clusterConfig.workspace;
|
|
38
|
+
this.payServices = clusterConfig.payServices;
|
|
39
|
+
this.dbServices = clusterConfig.dbServices;
|
|
40
|
+
this.reverseProxyServices = clusterConfig.reverseProxyServices;
|
|
41
|
+
this.egressProxy = clusterConfig.egressProxy;
|
|
42
|
+
this.javaApps = this.payServices.filter((service) => service.serviceType === PayServiceType.Java);
|
|
43
|
+
this.nodeApps = this.payServices.filter((service) => service.serviceType === PayServiceType.Node);
|
|
44
|
+
this.remoteJavaApps = this.javaApps.filter((service) => service.localBuild);
|
|
45
|
+
this.remoteNodeApps = this.nodeApps.filter((service) => service.localBuild);
|
|
46
|
+
this.localJavaApps = this.javaApps.filter((service) => service.localBuild);
|
|
47
|
+
this.localNodeApps = this.nodeApps.filter((service) => service.localBuild);
|
|
48
|
+
this.anyJavaApps = this.javaApps.length >= 0;
|
|
49
|
+
this.anyNodeApps = this.nodeApps.length >= 0;
|
|
50
|
+
this.requiresLocalStack = this.payServices.some((service) => service.requiresLocalStack);
|
|
51
|
+
this.requiresRedis = this.payServices.some((service) => service.usesRedis);
|
|
52
|
+
this.requiresEgressProxy = this.egressProxy !== undefined;
|
|
53
|
+
this.mountLocalNodeApps = clusterConfig.mountLocalNodeApps;
|
|
54
|
+
this.serviceURLEnvVars = [
|
|
55
|
+
this.javaApps.map((javaService) => envVarForJavaService(javaService)),
|
|
56
|
+
this.nodeApps.map((nodeService) => envVarForNodeService(nodeService))
|
|
57
|
+
].flat();
|
|
58
|
+
this.noProxyEnvVar = [
|
|
59
|
+
'localhost',
|
|
60
|
+
'127.0.0.1',
|
|
61
|
+
'172.18.0.253',
|
|
62
|
+
'localstack',
|
|
63
|
+
'redis',
|
|
64
|
+
this.payServices.map((service) => service.name),
|
|
65
|
+
this.dbServices.map((service) => service.name),
|
|
66
|
+
this.reverseProxyServices.map((service) => service.name),
|
|
67
|
+
this.egressProxy === undefined ? [] : this.egressProxy.name
|
|
68
|
+
].flat().join(',');
|
|
69
|
+
this.environmentOverridesPath = clusterConfig.environmentOverridesPath;
|
|
70
|
+
this.defaultServiceConfigsPath = clusterConfig.defaultServiceConfigsPath;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.PayLocalCluster = PayLocalCluster;
|
|
74
|
+
function envVarForJavaService(service) {
|
|
75
|
+
return {
|
|
76
|
+
name: `${service.name.toUpperCase()}_URL`,
|
|
77
|
+
value: `http://${service.name}:${service.port}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function envVarForNodeService(service) {
|
|
81
|
+
return {
|
|
82
|
+
name: `${service.name.toUpperCase()}_URL`,
|
|
83
|
+
value: `http://localhost:${service.port}`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
var PayServiceType;
|
|
87
|
+
(function (PayServiceType) {
|
|
88
|
+
PayServiceType["Java"] = "java";
|
|
89
|
+
PayServiceType["Node"] = "node";
|
|
90
|
+
})(PayServiceType || (PayServiceType = {}));
|
|
91
|
+
function loadClusterConfig(options, workspace, defaultServiceConfigsPath, environmentOverridesPath) {
|
|
92
|
+
const servicesConfig = loadServicesConfig();
|
|
93
|
+
const payServices = [];
|
|
94
|
+
for (const [, serviceConfig] of Object.entries(servicesConfig)) {
|
|
95
|
+
if (includeAppInCluster(options.cluster, options, serviceConfig)) {
|
|
96
|
+
payServices.push(payServiceFromPayServiceConfig(serviceConfig, options, environmentOverridesPath));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const dbServices = [];
|
|
100
|
+
for (const payService of payServices) {
|
|
101
|
+
if (payService.payServiceConfig.db) {
|
|
102
|
+
dbServices.push(dbServiceFromPayServiceConfig(payService.payServiceConfig));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const reverseProxyServices = [];
|
|
106
|
+
if (options.proxy) {
|
|
107
|
+
for (const payService of payServices) {
|
|
108
|
+
if (payService.hasProxy) {
|
|
109
|
+
reverseProxyServices.push(proxyServiceFromPayServiceConfig(payService.payServiceConfig, workspace));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const clusterConfig = {
|
|
114
|
+
name: options.cluster,
|
|
115
|
+
payServices,
|
|
116
|
+
dbServices,
|
|
117
|
+
reverseProxyServices,
|
|
118
|
+
workspace,
|
|
119
|
+
mountLocalNodeApps: options.mount_local_node_apps,
|
|
120
|
+
defaultServiceConfigsPath,
|
|
121
|
+
environmentOverridesPath
|
|
122
|
+
};
|
|
123
|
+
return new PayLocalCluster(clusterConfig);
|
|
124
|
+
}
|
|
125
|
+
exports.loadClusterConfig = loadClusterConfig;
|
|
126
|
+
function loadServicesConfig() {
|
|
127
|
+
const servicesConfigPath = node_path_1.default.join(__dirname, '..', '..', '..', '..', 'resources', 'pay-local', 'config', 'service_config.yaml');
|
|
128
|
+
const serviceConfigFileContents = node_fs_1.default.readFileSync(servicesConfigPath, 'utf8');
|
|
129
|
+
return yaml_1.default.parse(serviceConfigFileContents);
|
|
130
|
+
}
|
|
131
|
+
exports.loadServicesConfig = loadServicesConfig;
|
|
132
|
+
function includeAppInCluster(cluster, options, serviceConfig) {
|
|
133
|
+
if (cluster === 'all' || cluster === 'nuke' || serviceConfig.clusters.includes(cluster)) {
|
|
134
|
+
if (options.apps.length === 0 || options.apps.includes(serviceConfig.name)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
function payServiceFromPayServiceConfig(config, upOptions, environmentOverridesPath) {
|
|
141
|
+
const ports = [config.port];
|
|
142
|
+
if (config.admin_port !== undefined)
|
|
143
|
+
ports.push(config.admin_port);
|
|
144
|
+
const localBuild = upOptions.local.includes(config.name);
|
|
145
|
+
const sqsQueues = [];
|
|
146
|
+
if (config.queues !== undefined) {
|
|
147
|
+
for (const [envVarName, queueName] of Object.entries(config.queues)) {
|
|
148
|
+
sqsQueues.push({ envVarName, queueName });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const snsTopics = [];
|
|
152
|
+
if (config.sns_topics !== undefined) {
|
|
153
|
+
for (const [envVarName, topicName] of Object.entries(config.sns_topics)) {
|
|
154
|
+
snsTopics.push({ envVarName, topicName });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
name: config.name,
|
|
159
|
+
containerName: config.name,
|
|
160
|
+
port: config.port,
|
|
161
|
+
adminPort: config.admin_port,
|
|
162
|
+
debugPort: config.debug_port,
|
|
163
|
+
volumes: new Map(),
|
|
164
|
+
serviceType: config.type,
|
|
165
|
+
localBuild,
|
|
166
|
+
usesRedis: config.uses_redis === undefined ? false : config.uses_redis,
|
|
167
|
+
sqsQueues,
|
|
168
|
+
hasSQSQueues: sqsQueues.length > 0,
|
|
169
|
+
snsTopics,
|
|
170
|
+
hasSNSTopics: snsTopics.length > 0,
|
|
171
|
+
hasProxy: config.proxy === undefined ? false : config.proxy,
|
|
172
|
+
hasDB: config.db,
|
|
173
|
+
useEgressProxy: config.can_use_egress_proxy === undefined ? false : config.can_use_egress_proxy && upOptions.with_egress_proxy,
|
|
174
|
+
dependsOn: config.db ? [`${config.name}_db`] : [],
|
|
175
|
+
expose: [],
|
|
176
|
+
payServiceConfig: config,
|
|
177
|
+
imageTag: localBuild ? 'local' : 'latest-master',
|
|
178
|
+
requiresLocalStack: sqsQueues.length > 0 || snsTopics.length > 0,
|
|
179
|
+
entrypointOverrideLocal: config.entrypoint_override_local,
|
|
180
|
+
environmentOverrideFilePath: node_path_1.default.join(environmentOverridesPath, `${config.name}.env`)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function dbServiceFromPayServiceConfig(config) {
|
|
184
|
+
if (config.db_port === undefined) {
|
|
185
|
+
throw new Error(`The db specification for ${config.name} (which sets db: true) is missing the db_port specification`);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
name: `${config.name}_db`,
|
|
189
|
+
containerName: `${config.name}_db`,
|
|
190
|
+
dependsOn: [],
|
|
191
|
+
port: config.db_port,
|
|
192
|
+
expose: [],
|
|
193
|
+
volumes: new Map()
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function proxyServiceFromPayServiceConfig(config, workspace) {
|
|
197
|
+
if (config.proxy_port === undefined) {
|
|
198
|
+
throw new Error(`The proxy specification for ${config.name} (which sets proxy: true) is missing the proxy_port specification`);
|
|
199
|
+
}
|
|
200
|
+
// TODO: Deal with naxsi rules file and md5s
|
|
201
|
+
const naxsiSourcePath = naxsiConfigPathForPayServiceConfig(config, workspace);
|
|
202
|
+
return {
|
|
203
|
+
name: `${config.name}-proxy`,
|
|
204
|
+
containerName: `${config.name}-proxy`,
|
|
205
|
+
appName: config.name,
|
|
206
|
+
appPort: config.port,
|
|
207
|
+
port: config.proxy_port,
|
|
208
|
+
dependsOn: [],
|
|
209
|
+
expose: [],
|
|
210
|
+
volumes: new Map(),
|
|
211
|
+
naxsiRulesSourceFile: naxsiSourcePath,
|
|
212
|
+
naxsiRulesSourceFileMD5: (0, md5_js_1.md5File)(naxsiSourcePath)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function naxsiConfigPathForPayServiceConfig(service, workspace) {
|
|
216
|
+
const naxsiSourcePath = node_path_1.default.join(workspace, 'pay-infra', 'provisioning', 'terraform', 'modules', 'pay_microservices_v2', service.name, 'files', `${service.name}.naxsi`);
|
|
217
|
+
const nasxiSourcePathStat = node_fs_1.default.statSync(naxsiSourcePath, { throwIfNoEntry: false });
|
|
218
|
+
if (nasxiSourcePathStat === undefined) {
|
|
219
|
+
throw new Error(`Reverse proxies were requested, but the naxsi config file for '${service.name}' (${naxsiSourcePath}') was not found. Perhaps you need to checkout pay-infra into your workspace directory '${workspace}'?`);
|
|
220
|
+
}
|
|
221
|
+
if (!nasxiSourcePathStat.isFile()) {
|
|
222
|
+
throw new Error(`Reverse proxies were requested, but the naxsi config file for '${service.name}' (${naxsiSourcePath}') is not a regular file. Perhaps you need to checkout pay-infra into your workspace directory '${workspace}'?`);
|
|
223
|
+
}
|
|
224
|
+
return naxsiSourcePath;
|
|
225
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderDockerCompose = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const handlebars_1 = __importDefault(require("handlebars"));
|
|
10
|
+
/**
|
|
11
|
+
* Renders the 'if' path if both a and b are true, otherwise renders the 'else' path
|
|
12
|
+
*/
|
|
13
|
+
handlebars_1.default.registerHelper('ifBoth', (a, b, options) => {
|
|
14
|
+
if (a && b) {
|
|
15
|
+
return options.fn(this);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return options.inverse(this);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Renders the 'if' path if the file specified exists, otherwise renders the 'else' path
|
|
23
|
+
*/
|
|
24
|
+
handlebars_1.default.registerHelper('ifFileExists', (filePath, options) => {
|
|
25
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
26
|
+
return options.fn(this);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return options.inverse(this);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Renders the handlebars template, writes it into the destination directory and returns the path
|
|
34
|
+
* to the file
|
|
35
|
+
*/
|
|
36
|
+
function renderDockerCompose(cluster, destinationDirectory) {
|
|
37
|
+
const tempalteSource = loadTemplate();
|
|
38
|
+
const template = handlebars_1.default.compile(tempalteSource);
|
|
39
|
+
const destinationFilePath = `${node_fs_1.default.realpathSync(destinationDirectory)}/${cluster.name}.yaml`;
|
|
40
|
+
node_fs_1.default.writeFileSync(destinationFilePath, template(cluster), { mode: 0o660 });
|
|
41
|
+
return destinationFilePath;
|
|
42
|
+
}
|
|
43
|
+
exports.renderDockerCompose = renderDockerCompose;
|
|
44
|
+
function loadTemplate() {
|
|
45
|
+
return node_fs_1.default.readFileSync(node_path_1.default.join(__dirname, '..', '..', '..', '..', 'resources', 'pay-local', 'templates', 'docker-compose.hbs'), 'utf8');
|
|
46
|
+
}
|