@gnar-engine/cli 1.0.3 → 1.0.5
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/bootstrap/deploy.localdev.yml +44 -30
- package/bootstrap/secrets.localdev.yml +20 -14
- package/bootstrap/services/control/src/config.js +4 -0
- package/bootstrap/services/{agent → page}/Dockerfile +3 -3
- package/bootstrap/services/page/package.json +16 -0
- package/bootstrap/services/page/src/app.js +50 -0
- package/bootstrap/services/page/src/commands/block.handler.js +94 -0
- package/bootstrap/services/page/src/commands/page.handler.js +167 -0
- package/bootstrap/services/page/src/config.js +62 -0
- package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
- package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
- package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
- package/bootstrap/services/page/src/policies/block.policy.js +50 -0
- package/bootstrap/services/page/src/policies/page.policy.js +49 -0
- package/bootstrap/services/page/src/schema/page.schema.js +139 -0
- package/bootstrap/services/page/src/services/block.service.js +83 -0
- package/bootstrap/services/page/src/services/page.service.js +83 -0
- package/bootstrap/services/portal/Dockerfile +7 -10
- package/bootstrap/services/portal/README.md +66 -15
- package/bootstrap/services/portal/index.html +13 -0
- package/bootstrap/services/portal/nginx.conf +5 -12
- package/bootstrap/services/portal/package.json +27 -53
- package/bootstrap/services/portal/public/vite.svg +1 -0
- package/bootstrap/services/portal/react-router.config.js +7 -0
- package/bootstrap/services/portal/src/App.jsx +16 -0
- package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
- package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
- package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
- package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
- package/bootstrap/services/portal/src/assets/react.svg +1 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
- package/bootstrap/services/portal/src/{ui/customSelect → components/CustomSelect}/CustomSelect.jsx +21 -3
- package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
- package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
- package/bootstrap/services/portal/src/css/style.css +446 -742
- package/bootstrap/services/portal/src/data/pages.data.js +10 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
- package/bootstrap/services/portal/src/{ui/customSelect/customSelect.less → elements/CustomSelect/CustomSelect.less} +17 -7
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
- package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
- package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
- package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
- package/bootstrap/services/portal/src/main.jsx +51 -0
- package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
- package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
- package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
- package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
- package/bootstrap/services/portal/src/services/block.js +28 -0
- package/bootstrap/services/portal/src/services/client.js +67 -0
- package/bootstrap/services/portal/src/services/gravatar.js +14 -0
- package/bootstrap/services/portal/src/services/page.js +28 -0
- package/bootstrap/services/portal/src/services/storage.js +62 -0
- package/bootstrap/services/portal/src/services/user.js +41 -0
- package/bootstrap/services/portal/src/slices/authSlice.js +44 -14
- package/bootstrap/services/portal/src/store/configureStore.js +1 -3
- package/bootstrap/services/portal/src/style/cards.less +57 -0
- package/bootstrap/services/portal/src/{styles → style}/global.less +90 -45
- package/bootstrap/services/portal/src/style/icons.less +21 -0
- package/bootstrap/services/portal/src/style/inputs.less +52 -0
- package/bootstrap/services/portal/src/style/main.less +28 -0
- package/bootstrap/services/portal/src/utils/utils.js +9 -0
- package/bootstrap/services/portal/vite.config.js +12 -0
- package/bootstrap/services/user/Dockerfile +1 -1
- package/bootstrap/services/user/src/app.js +6 -1
- package/bootstrap/services/user/src/commands/user.handler.js +0 -3
- package/bootstrap/services/user/src/config.js +5 -1
- package/bootstrap/services/user/src/policies/user.policy.js +3 -1
- package/bootstrap/services/user/src/tests/commands/user.test.js +22 -0
- package/install-from-clone.sh +30 -0
- package/package.json +1 -1
- package/src/cli.js +8 -0
- package/src/config.js +2 -1
- package/src/dev/commands.js +10 -2
- package/src/dev/dev.service.js +167 -52
- package/src/provisioner/Dockerfile +27 -0
- package/src/provisioner/package.json +19 -0
- package/src/provisioner/src/app.js +56 -0
- package/src/provisioner/src/services/mongodb.js +58 -0
- package/src/provisioner/src/services/mysql.js +51 -0
- package/src/provisioner/src/services/secrets.js +84 -0
- package/src/scaffolder/commands.js +12 -5
- package/src/scaffolder/scaffolder.handler.js +256 -58
- package/templates/service/Dockerfile.hbs +4 -1
- package/templates/service/package.json.hbs +14 -16
- package/templates/service/src/app.js.hbs +53 -0
- package/templates/service/{commands → src/commands}/{{serviceName}}.handler.js.hbs +2 -3
- package/templates/service/src/mongodb.config.js.hbs +49 -0
- package/templates/service/{config.js.hbs → src/mysql.config.js.hbs} +10 -0
- package/{bootstrap/services/agent/src/schema/Agent.schema.js → templates/service/src/schema/{{serviceName}}.schema.js.hbs} +3 -3
- package/templates/service/src/services/mongodb.{{serviceName}}.service.js.hbs +70 -0
- package/bootstrap/services/agent/notes.md +0 -28
- package/bootstrap/services/agent/package.json +0 -16
- package/bootstrap/services/agent/src/app.js +0 -52
- package/bootstrap/services/agent/src/commands/agent.handler.js +0 -104
- package/bootstrap/services/agent/src/config.js +0 -52
- package/bootstrap/services/agent/src/controllers/http.controller.js +0 -44
- package/bootstrap/services/agent/src/controllers/message.controller.js +0 -51
- package/bootstrap/services/agent/src/db/migrations/01-init.js +0 -50
- package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +0 -36
- package/bootstrap/services/agent/src/policies/agent.policy.js +0 -13
- package/bootstrap/services/agent/src/services/agent.service.js +0 -259
- package/bootstrap/services/agent/src/services/chatgpt.service.js +0 -46
- package/bootstrap/services/agent/src/services/manifest.service.js +0 -21
- package/bootstrap/services/portal/Dockerfile.remote +0 -40
- package/bootstrap/services/portal/public/favicon.ico +0 -0
- package/bootstrap/services/portal/public/gnar-white.png +0 -0
- package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
- package/bootstrap/services/portal/public/index.html +0 -43
- package/bootstrap/services/portal/public/logo192.png +0 -0
- package/bootstrap/services/portal/public/logo512.png +0 -0
- package/bootstrap/services/portal/public/manifest.json +0 -25
- package/bootstrap/services/portal/public/robots.txt +0 -3
- package/bootstrap/services/portal/src/App.js +0 -56
- package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +0 -1
- package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +0 -1
- package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +0 -1
- package/bootstrap/services/portal/src/assets/activity.svg +0 -3
- package/bootstrap/services/portal/src/assets/arrow.svg +0 -3
- package/bootstrap/services/portal/src/assets/bin-white.svg +0 -3
- package/bootstrap/services/portal/src/assets/bin.svg +0 -3
- package/bootstrap/services/portal/src/assets/check.svg +0 -3
- package/bootstrap/services/portal/src/assets/chevron.svg +0 -3
- package/bootstrap/services/portal/src/assets/contact.svg +0 -3
- package/bootstrap/services/portal/src/assets/dots-vertical.svg +0 -5
- package/bootstrap/services/portal/src/assets/eye-off.svg +0 -3
- package/bootstrap/services/portal/src/assets/eye.svg +0 -4
- package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +0 -47
- package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +0 -47
- package/bootstrap/services/portal/src/assets/gnar_engine.svg +0 -3
- package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
- package/bootstrap/services/portal/src/assets/home.svg +0 -3
- package/bootstrap/services/portal/src/assets/link.svg +0 -3
- package/bootstrap/services/portal/src/assets/lock.svg +0 -3
- package/bootstrap/services/portal/src/assets/package.svg +0 -4
- package/bootstrap/services/portal/src/assets/raffle.svg +0 -3
- package/bootstrap/services/portal/src/assets/settings.svg +0 -4
- package/bootstrap/services/portal/src/assets/shopping-bag.svg +0 -3
- package/bootstrap/services/portal/src/assets/user-black.svg +0 -3
- package/bootstrap/services/portal/src/assets/user.svg +0 -3
- package/bootstrap/services/portal/src/assets/users.svg +0 -3
- package/bootstrap/services/portal/src/assets/wallet.svg +0 -3
- package/bootstrap/services/portal/src/data/data.js +0 -70
- package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +0 -32
- package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +0 -160
- package/bootstrap/services/portal/src/features/crud/crudEdit.less +0 -230
- package/bootstrap/services/portal/src/features/crud/crudList.less +0 -134
- package/bootstrap/services/portal/src/features/crud/crudPage.less +0 -31
- package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +0 -108
- package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +0 -243
- package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +0 -109
- package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +0 -315
- package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +0 -104
- package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +0 -388
- package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +0 -104
- package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +0 -208
- package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +0 -110
- package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +0 -261
- package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +0 -107
- package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +0 -402
- package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +0 -30
- package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +0 -113
- package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +0 -56
- package/bootstrap/services/portal/src/features/loginForm/loginForm.less +0 -56
- package/bootstrap/services/portal/src/features/notes/Notes.jsx +0 -18
- package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +0 -96
- package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +0 -74
- package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +0 -102
- package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +0 -24
- package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +0 -99
- package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +0 -46
- package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +0 -64
- package/bootstrap/services/portal/src/features/sidebar/sidebar.less +0 -49
- package/bootstrap/services/portal/src/features/skus/Skus.jsx +0 -109
- package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +0 -44
- package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +0 -32
- package/bootstrap/services/portal/src/features/user/User.jsx +0 -54
- package/bootstrap/services/portal/src/features/user/user.less +0 -57
- package/bootstrap/services/portal/src/includes/utilities.js +0 -259
- package/bootstrap/services/portal/src/index.js +0 -14
- package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +0 -50
- package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +0 -17
- package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +0 -48
- package/bootstrap/services/portal/src/layouts/loginLayout.less +0 -33
- package/bootstrap/services/portal/src/layouts/portalLayout.less +0 -67
- package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +0 -199
- package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +0 -17
- package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +0 -10
- package/bootstrap/services/portal/src/pages/login/Login.jsx +0 -15
- package/bootstrap/services/portal/src/pages/login/login.less +0 -10
- package/bootstrap/services/portal/src/pages/orders/Orders.jsx +0 -199
- package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +0 -15
- package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +0 -15
- package/bootstrap/services/portal/src/pages/payments/Payments.jsx +0 -10
- package/bootstrap/services/portal/src/pages/portal/Portal.jsx +0 -43
- package/bootstrap/services/portal/src/pages/products/Products.jsx +0 -212
- package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +0 -124
- package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +0 -186
- package/bootstrap/services/portal/src/pages/reports/Reports.jsx +0 -10
- package/bootstrap/services/portal/src/pages/settings/Settings.jsx +0 -10
- package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +0 -199
- package/bootstrap/services/portal/src/pages/users/Users.jsx +0 -193
- package/bootstrap/services/portal/src/pages/users/users.less +0 -25
- package/bootstrap/services/portal/src/styles/inputs.less +0 -157
- package/bootstrap/services/portal/src/styles/main.less +0 -26
- package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +0 -97
- package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +0 -23
- package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +0 -17
- package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +0 -42
- package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +0 -63
- package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +0 -63
- package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +0 -19
- package/bootstrap/services/portal/src/ui/loader/Loader.jsx +0 -12
- package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +0 -23
- package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +0 -29
- package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +0 -69
- package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +0 -27
- package/bootstrap/services/user/src/tests/user.test.js +0 -126
- package/templates/service/app.js.hbs +0 -38
- package/templates/service/schema/{{serviceName}}.schema.js.hbs +0 -14
- /package/bootstrap/services/portal/src/{ui/saveButton/saveButton.less → components/CustomSelect/CustomSelect.less} +0 -0
- /package/templates/service/{controllers → src/controllers}/http.controller.js.hbs +0 -0
- /package/templates/service/{controllers → src/controllers}/message.controller.js.hbs +0 -0
- /package/templates/service/{db → src/mysql.db}/migrations/01-init.js.hbs +0 -0
- /package/templates/service/{db → src/mysql.db}/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +0 -0
- /package/templates/service/{policies → src/policies}/{{serviceName}}.policy.js.hbs +0 -0
- /package/templates/service/{services/{{serviceName}}.service.js.hbs → src/services/mysql.{{serviceName}}.service.js.hbs} +0 -0
package/src/dev/commands.js
CHANGED
|
@@ -12,6 +12,8 @@ export function registerDevCommands(program) {
|
|
|
12
12
|
.description('🛠️ Up Development Containers')
|
|
13
13
|
.option('-b, --build', 'Ruild without cache')
|
|
14
14
|
.option('-d, --detach', 'Run containers in background')
|
|
15
|
+
.option('-t --test', 'Run the tests with ephemeral databases')
|
|
16
|
+
.option('--test-service <service>', 'Run the tests for the specified service with ephemeral databases')
|
|
15
17
|
.addOption(new Option('--core-dev').hideHelp())
|
|
16
18
|
.action(async (options) => {
|
|
17
19
|
let response = {};
|
|
@@ -27,13 +29,19 @@ export function registerDevCommands(program) {
|
|
|
27
29
|
// Change to the active profile directory
|
|
28
30
|
const projectDir = activeProfile.PROJECT_DIR;
|
|
29
31
|
|
|
32
|
+
if (options.testService) {
|
|
33
|
+
options.test = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
try {
|
|
31
37
|
up({
|
|
32
38
|
projectDir: projectDir,
|
|
33
39
|
build: options.build || false,
|
|
34
40
|
detach: options.detach || false,
|
|
35
|
-
coreDev: options.coreDev || false
|
|
36
|
-
|
|
41
|
+
coreDev: options.coreDev || false,
|
|
42
|
+
test: options.test || false,
|
|
43
|
+
testService: options.testService || ''
|
|
44
|
+
});
|
|
37
45
|
} catch (err) {
|
|
38
46
|
console.error("❌ Error running containers:", err.message);
|
|
39
47
|
process.exit(1);
|
package/src/dev/dev.service.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from "fs/promises";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import yaml from "js-yaml";
|
|
7
7
|
import { gnarEngineCliConfig } from "../config.js";
|
|
8
|
+
import { directories } from "../cli.js";
|
|
8
9
|
|
|
9
10
|
const docker = new Docker();
|
|
10
11
|
|
|
@@ -18,8 +19,18 @@ const docker = new Docker();
|
|
|
18
19
|
* @param {boolean} [options.build=false] - Whether to re-build images
|
|
19
20
|
* @param {boolean} [options.detached=false] - Whether to run containers in background
|
|
20
21
|
* @param {boolean} [options.coreDev=false] - Whether to run in core development mode (requires access to core source)
|
|
22
|
+
* @param {boolean} [options.test=false] - Whether to run tests with ephemeral databases
|
|
23
|
+
* @param {string} [options.testService=''] - The service to run tests for (only applicable if test=true)
|
|
24
|
+
* @param {boolean} [options.removeOrphans=true] - Whether to remove orphaned containers
|
|
21
25
|
*/
|
|
22
|
-
export async function up({ projectDir, build = false, detached = false, coreDev = false }) {
|
|
26
|
+
export async function up({ projectDir, build = false, detached = false, coreDev = false, test = false, testService = '', removeOrphans = true }) {
|
|
27
|
+
|
|
28
|
+
// core dev
|
|
29
|
+
if (coreDev) {
|
|
30
|
+
const fileDir = path.dirname(new URL(import.meta.url).pathname);
|
|
31
|
+
projectDir = path.resolve(fileDir, "../../bootstrap/");
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
// parse config
|
|
24
35
|
const configPath = path.join(projectDir, "deploy.localdev.yml");
|
|
25
36
|
const secretsPath = path.join(projectDir, "secrets.localdev.yml");
|
|
@@ -33,8 +44,13 @@ export async function up({ projectDir, build = false, detached = false, coreDev
|
|
|
33
44
|
|
|
34
45
|
// create nginx.conf dynamically from configPath
|
|
35
46
|
const nginxConfPath = path.join(gnarHiddenDir, "nginx", "nginx.conf");
|
|
47
|
+
const serviceConfDir = path.join(gnarHiddenDir, "nginx", "service_conf")
|
|
48
|
+
await fs.mkdir(serviceConfDir, { recursive: true });
|
|
49
|
+
|
|
36
50
|
const nginxConf = await createDynamicNginxConf({
|
|
37
|
-
config: parsedConfig.config
|
|
51
|
+
config: parsedConfig.config,
|
|
52
|
+
projectDir: projectDir,
|
|
53
|
+
serviceConfDir: serviceConfDir
|
|
38
54
|
});
|
|
39
55
|
await fs.writeFile(nginxConfPath, nginxConf);
|
|
40
56
|
|
|
@@ -45,7 +61,9 @@ export async function up({ projectDir, build = false, detached = false, coreDev
|
|
|
45
61
|
secrets: parsedSecrets,
|
|
46
62
|
gnarHiddenDir: gnarHiddenDir,
|
|
47
63
|
projectDir: projectDir,
|
|
48
|
-
coreDev: coreDev
|
|
64
|
+
coreDev: coreDev,
|
|
65
|
+
test: test,
|
|
66
|
+
testService: testService
|
|
49
67
|
});
|
|
50
68
|
await fs.writeFile(dockerComposePath, yaml.dump(dockerCompose));
|
|
51
69
|
|
|
@@ -60,6 +78,10 @@ export async function up({ projectDir, build = false, detached = false, coreDev
|
|
|
60
78
|
args.push("-d");
|
|
61
79
|
}
|
|
62
80
|
|
|
81
|
+
if (removeOrphans) {
|
|
82
|
+
args.push("--remove-orphans")
|
|
83
|
+
}
|
|
84
|
+
|
|
63
85
|
const processRef = spawn(
|
|
64
86
|
"docker-compose",
|
|
65
87
|
args,
|
|
@@ -121,19 +143,41 @@ export async function down({ projectDir, allContainers = false }) {
|
|
|
121
143
|
* Create dynamic nginx.conf file for running application locally
|
|
122
144
|
*
|
|
123
145
|
* @param {object} config
|
|
124
|
-
* @param {string}
|
|
146
|
+
* @param {string} serviceConfDir
|
|
147
|
+
* @param {string} projectDir
|
|
125
148
|
*/
|
|
126
|
-
export async function createDynamicNginxConf({ config,
|
|
149
|
+
export async function createDynamicNginxConf({ config, serviceConfDir, projectDir }) {
|
|
127
150
|
// Start with the static parts of nginx.conf
|
|
128
151
|
let nginxConf = `
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
152
|
+
events { worker_connections 1024; }
|
|
153
|
+
|
|
154
|
+
http {
|
|
155
|
+
server {
|
|
156
|
+
listen 80;
|
|
157
|
+
server_name ${config.namespace};
|
|
158
|
+
include /etc/nginx/service_conf/*.conf;
|
|
159
|
+
|
|
133
160
|
`;
|
|
134
161
|
|
|
135
162
|
// Loop over each service
|
|
136
163
|
for (const service of config.services || []) {
|
|
164
|
+
// Check if override is present and add conf to service_conf dir
|
|
165
|
+
const serviceDir = path.join(projectDir, 'services', service.name);
|
|
166
|
+
|
|
167
|
+
if (await fs.stat(serviceDir).then(() => true).catch(() => false)) {
|
|
168
|
+
const overridePath = path.join(serviceDir, 'nginx.conf');
|
|
169
|
+
if (await fs.stat(overridePath).then(() => true).catch(() => false)) {
|
|
170
|
+
const overrideConf = await fs.readFile(overridePath, 'utf8');
|
|
171
|
+
|
|
172
|
+
// write to service_conf directory
|
|
173
|
+
const serviceConfPath = path.join(serviceConfDir, `${service.name}.conf`);
|
|
174
|
+
await fs.writeFile(serviceConfPath, overrideConf);
|
|
175
|
+
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Otherwise create generic conf block
|
|
137
181
|
const serviceName = service.name;
|
|
138
182
|
const paths = service.listener_rules?.paths || [];
|
|
139
183
|
const containerPort = service.ports && service.ports.length > 0 ? service.ports[0].split(':')[1] : '3000';
|
|
@@ -155,8 +199,8 @@ export async function createDynamicNginxConf({ config, outputPath }) {
|
|
|
155
199
|
|
|
156
200
|
// Close server and http blocks
|
|
157
201
|
nginxConf += `
|
|
202
|
+
}
|
|
158
203
|
}
|
|
159
|
-
}
|
|
160
204
|
`;
|
|
161
205
|
|
|
162
206
|
return nginxConf;
|
|
@@ -170,12 +214,56 @@ export async function createDynamicNginxConf({ config, outputPath }) {
|
|
|
170
214
|
* @param {string} gnarHiddenDir
|
|
171
215
|
* @param {string} projectDir
|
|
172
216
|
* @param {boolean} coreDev - Whether to volume mount the core source code
|
|
217
|
+
* @param {boolean} test - Whether to run tests with ephemeral databases
|
|
218
|
+
* @param {string} testService - The service to run tests for (only applicable if test=true)
|
|
219
|
+
* @param {boolean} attachAll - Whether to attach to all containers' stdio (otherwise databases and message queue are detached)
|
|
173
220
|
*/
|
|
174
|
-
async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, projectDir, coreDev = false }) {
|
|
221
|
+
async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, projectDir, coreDev = false, test = false, testService, attachAll = false }) {
|
|
175
222
|
let mysqlPortsCounter = 3306;
|
|
176
223
|
let mongoPortsCounter = 27017;
|
|
224
|
+
let mysqlHostsRequired = [];
|
|
225
|
+
let mongoHostsRequired = [];
|
|
177
226
|
const services = {};
|
|
178
|
-
|
|
227
|
+
|
|
228
|
+
// test mode env var adjustments
|
|
229
|
+
for (const svc of config.services) {
|
|
230
|
+
if (test) {
|
|
231
|
+
if (secrets.services?.[svc.name]?.MYSQL_HOST) {
|
|
232
|
+
secrets.services[svc.name].MYSQL_HOST = 'db-mysql-test';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (secrets.services?.[svc.name]) {
|
|
236
|
+
secrets.services[svc.name].NODE_ENV = 'test';
|
|
237
|
+
|
|
238
|
+
if (testService && svc.name === testService) {
|
|
239
|
+
secrets.services[svc.name].RUN_TESTS = 'true';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// provision the provisioner service
|
|
246
|
+
services['provisioner'] = {
|
|
247
|
+
container_name: `ge-${config.environment}-${config.namespace}-provisioner`,
|
|
248
|
+
image: `ge-${config.environment}-${config.namespace}-provisioner`,
|
|
249
|
+
build: {
|
|
250
|
+
context: directories.provisioner,
|
|
251
|
+
dockerfile: `./Dockerfile`
|
|
252
|
+
},
|
|
253
|
+
environment: {
|
|
254
|
+
PROVISIONER_SECRETS: JSON.stringify(secrets)
|
|
255
|
+
},
|
|
256
|
+
volumes: [
|
|
257
|
+
`${directories.provisioner}/src:/usr/gnar_engine/app/src`
|
|
258
|
+
],
|
|
259
|
+
restart: 'no',
|
|
260
|
+
attach: attachAll
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (coreDev) {
|
|
264
|
+
services['provisioner'].volumes.push(`../../../core/:${gnarEngineCliConfig.corePath}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
179
267
|
// nginx
|
|
180
268
|
services['nginx'] = {
|
|
181
269
|
image: 'nginx:latest',
|
|
@@ -185,9 +273,11 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
185
273
|
"443:443"
|
|
186
274
|
],
|
|
187
275
|
volumes: [
|
|
188
|
-
`${gnarHiddenDir}/nginx/nginx.conf:/etc/nginx/nginx.conf
|
|
276
|
+
`${gnarHiddenDir}/nginx/nginx.conf:/etc/nginx/nginx.conf`,
|
|
277
|
+
`${gnarHiddenDir}/nginx/service_conf:/etc/nginx/service_conf`
|
|
189
278
|
],
|
|
190
|
-
restart: 'always'
|
|
279
|
+
restart: 'always',
|
|
280
|
+
attach: attachAll
|
|
191
281
|
}
|
|
192
282
|
|
|
193
283
|
// rabbit
|
|
@@ -202,7 +292,8 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
202
292
|
RABBITMQ_DEFAULT_USER: secrets.global.RABBITMQ_USER || '',
|
|
203
293
|
RABBITMQ_DEFAULT_PASS: secrets.global.RABBITMQ_PASS || ''
|
|
204
294
|
},
|
|
205
|
-
restart: 'always'
|
|
295
|
+
restart: 'always',
|
|
296
|
+
attach: attachAll
|
|
206
297
|
}
|
|
207
298
|
|
|
208
299
|
// services
|
|
@@ -211,7 +302,7 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
211
302
|
// env variables
|
|
212
303
|
const serviceEnvVars = secrets.services?.[svc.name] || {};
|
|
213
304
|
const localisedServiceEnvVars = {};
|
|
214
|
-
|
|
305
|
+
|
|
215
306
|
for (const [key, value] of Object.entries(serviceEnvVars)) {
|
|
216
307
|
localisedServiceEnvVars[svc.name.toUpperCase() + '_' + key] = value;
|
|
217
308
|
}
|
|
@@ -221,6 +312,14 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
221
312
|
...(localisedServiceEnvVars || {})
|
|
222
313
|
};
|
|
223
314
|
|
|
315
|
+
// test mode adjustments
|
|
316
|
+
if (test) {
|
|
317
|
+
if (svc.depends_on && svc.depends_on.includes('db-mysql')) {
|
|
318
|
+
svc.depends_on = svc.depends_on.filter(d => d !== 'db-mysql');
|
|
319
|
+
svc.depends_on.push('db-mysql-test');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
224
323
|
// service block
|
|
225
324
|
services[`${svc.name}-service`] = {
|
|
226
325
|
container_name: `ge-${config.environment}-${config.namespace}-${svc.name}`,
|
|
@@ -239,71 +338,86 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
239
338
|
restart: 'always'
|
|
240
339
|
};
|
|
241
340
|
|
|
242
|
-
// add the core source code mount if in
|
|
341
|
+
// add the core source code mount if in coreDev mode
|
|
243
342
|
if (coreDev) {
|
|
244
|
-
services[`${svc.name}-service`].volumes.push(`../../../
|
|
343
|
+
services[`${svc.name}-service`].volumes.push(`../../../core/:${gnarEngineCliConfig.corePath}`);
|
|
245
344
|
}
|
|
246
345
|
|
|
247
|
-
//
|
|
346
|
+
// check if mysql service required
|
|
248
347
|
if (
|
|
249
348
|
serviceEnvVars.MYSQL_HOST &&
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
serviceEnvVars.
|
|
253
|
-
|
|
349
|
+
secrets.provision?.MYSQL_ROOT_PASSWORD
|
|
350
|
+
) {
|
|
351
|
+
mysqlHostsRequired.push(serviceEnvVars.MYSQL_HOST);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// add a mongodb instance if required
|
|
355
|
+
if (
|
|
356
|
+
serviceEnvVars.MONGO_HOST &&
|
|
357
|
+
secrets.provision?.MONGO_ROOT_PASSWORD
|
|
254
358
|
) {
|
|
255
|
-
|
|
256
|
-
|
|
359
|
+
mongoHostsRequired.push(serviceEnvVars.MONGO_HOST);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// add mysql if required
|
|
364
|
+
if (mysqlHostsRequired.length > 0) {
|
|
365
|
+
for (const host of mysqlHostsRequired) {
|
|
366
|
+
if (services[host]) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
services[host] = {
|
|
371
|
+
container_name: `ge-${config.environment}-${config.namespace}-${host}`,
|
|
257
372
|
image: 'mysql',
|
|
258
373
|
ports: [
|
|
259
374
|
`${mysqlPortsCounter}:${mysqlPortsCounter}`
|
|
260
375
|
],
|
|
261
376
|
restart: 'always',
|
|
262
377
|
environment: {
|
|
263
|
-
MYSQL_HOST:
|
|
264
|
-
|
|
265
|
-
MYSQL_USER: serviceEnvVars.MYSQL_USER,
|
|
266
|
-
MYSQL_PASSWORD: serviceEnvVars.MYSQL_PASSWORD,
|
|
267
|
-
MYSQL_RANDOM_ROOT_PASSWORD: serviceEnvVars.MYSQL_RANDOM_ROOT_PASSWORD,
|
|
378
|
+
MYSQL_HOST: host,
|
|
379
|
+
MYSQL_ROOT_PASSWORD: secrets.provision.MYSQL_ROOT_PASSWORD
|
|
268
380
|
},
|
|
269
381
|
volumes: [
|
|
270
|
-
`${gnarHiddenDir}/data/${
|
|
271
|
-
]
|
|
272
|
-
|
|
382
|
+
`${gnarHiddenDir}/data/${host}-data:/var/lib/mysql`
|
|
383
|
+
],
|
|
384
|
+
attach: attachAll
|
|
385
|
+
};
|
|
273
386
|
|
|
274
|
-
// increment mysql port for next service as required
|
|
275
387
|
mysqlPortsCounter++;
|
|
276
388
|
}
|
|
277
389
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
390
|
+
services['provisioner'].depends_on = [...new Set(mysqlHostsRequired)];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// add mongo hosts if required
|
|
394
|
+
if (mongoHostsRequired.length > 0) {
|
|
395
|
+
for (const host of mongoHostsRequired) {
|
|
396
|
+
if (services[host]) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
services[host] = {
|
|
401
|
+
container_name: `ge-${config.environment}-${config.namespace}-${host}`,
|
|
402
|
+
image: 'mongo:latest',
|
|
288
403
|
ports: [
|
|
289
|
-
`${mongoPortsCounter}
|
|
404
|
+
`${mongoPortsCounter}:27017`
|
|
290
405
|
],
|
|
291
406
|
restart: 'always',
|
|
292
407
|
environment: {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
[`${svc.name.toUpperCase()}_MONGO_USER`]: serviceEnvVars.MONGO_USER,
|
|
296
|
-
[`${svc.name.toUpperCase()}_MONGO_PASSWORD`]: serviceEnvVars.MONGO_PASSWORD,
|
|
408
|
+
MONGO_INITDB_ROOT_USERNAME: 'root',
|
|
409
|
+
MONGO_INITDB_ROOT_PASSWORD: secrets.provision.MONGO_ROOT_PASSWORD
|
|
297
410
|
},
|
|
298
411
|
volumes: [
|
|
299
|
-
`${gnarHiddenDir}/
|
|
300
|
-
|
|
412
|
+
`${gnarHiddenDir}/data/${host}-data:/data/db`,
|
|
413
|
+
'./mongo-init-scripts:/docker-entrypoint-initdb.d'
|
|
414
|
+
],
|
|
415
|
+
attach: attachAll
|
|
301
416
|
};
|
|
302
417
|
|
|
303
418
|
// increment mongo port for next service as required
|
|
304
419
|
mongoPortsCounter++;
|
|
305
420
|
}
|
|
306
|
-
|
|
307
421
|
}
|
|
308
422
|
|
|
309
423
|
return {
|
|
@@ -318,3 +432,4 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
318
432
|
async function assertGnarEngineHiddenDir(gnarHiddenDir) {
|
|
319
433
|
await fs.mkdir(gnarHiddenDir, { recursive: true });
|
|
320
434
|
}
|
|
435
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Dockerfile for the ephemeral provisioning service
|
|
2
|
+
#
|
|
3
|
+
# This service is responsible for asserting the databases
|
|
4
|
+
# & database users, or other single runtime provisioning tasks.
|
|
5
|
+
|
|
6
|
+
FROM node:20-alpine
|
|
7
|
+
|
|
8
|
+
# Set the working directory
|
|
9
|
+
WORKDIR /usr/gnar_engine/app
|
|
10
|
+
|
|
11
|
+
# Define a global env var
|
|
12
|
+
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
13
|
+
|
|
14
|
+
# Copy package.json and package-lock.json
|
|
15
|
+
COPY ./package*.json ./
|
|
16
|
+
|
|
17
|
+
# Copy source
|
|
18
|
+
COPY ./src ./src
|
|
19
|
+
|
|
20
|
+
# Install nodemon
|
|
21
|
+
RUN npm install -g nodemon
|
|
22
|
+
|
|
23
|
+
# Install app dependencies
|
|
24
|
+
RUN npm install
|
|
25
|
+
|
|
26
|
+
# Start the application
|
|
27
|
+
CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gnar_engine_user",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Gnar Engine - User Service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node ./src/app.js",
|
|
8
|
+
"start:dev": "nodemon --watch ./src ./src/app.js",
|
|
9
|
+
"test": "jest --watchAll --verbose"
|
|
10
|
+
},
|
|
11
|
+
"author": "Gnar Software Ltd.",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@gnar-engine/core": "^1.0.1",
|
|
15
|
+
"dotenv": "^16.4.7",
|
|
16
|
+
"mongodb": "^5.9.2",
|
|
17
|
+
"mysql2": "^3.12.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mysqlService } from './services/mysql.js';
|
|
2
|
+
import { mongoService } from './services/mongodb.js';
|
|
3
|
+
import { secrets } from './services/secrets.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialise service
|
|
7
|
+
*/
|
|
8
|
+
export const initService = async () => {
|
|
9
|
+
|
|
10
|
+
console.log('G n a r E n g i n e | Provisioner provisioning databases...');
|
|
11
|
+
|
|
12
|
+
let provisionerSecrets;
|
|
13
|
+
|
|
14
|
+
// get all secrets
|
|
15
|
+
try {
|
|
16
|
+
provisionerSecrets = JSON.parse(process.env.PROVISIONER_SECRETS);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('Error parsing provisioner secrets', error);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// collate databases to provision from secrets
|
|
23
|
+
const mysqlDatabases = secrets.collateMysqlDatabases(provisionerSecrets);
|
|
24
|
+
const mongoDatabases = secrets.collateMongoDatabases(provisionerSecrets);
|
|
25
|
+
|
|
26
|
+
// provision mysql databases
|
|
27
|
+
if (mysqlDatabases) {
|
|
28
|
+
for (const [key, value] of Object.entries(mysqlDatabases)) {
|
|
29
|
+
mysqlService.provisionDatabase({
|
|
30
|
+
host: value.host,
|
|
31
|
+
database: value.database,
|
|
32
|
+
user: value.user,
|
|
33
|
+
password: value.password,
|
|
34
|
+
rootPassword: provisionerSecrets.provision.MYSQL_ROOT_PASSWORD
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
console.log('No MySQL databases to provision.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (mongoDatabases) {
|
|
42
|
+
for (const [key, value] of Object.entries(mongoDatabases)) {
|
|
43
|
+
mongoService.provisionDatabase({
|
|
44
|
+
host: value.host,
|
|
45
|
+
database: value.database,
|
|
46
|
+
user: value.user,
|
|
47
|
+
password: value.password,
|
|
48
|
+
rootPassword: provisionerSecrets.provision.MONGO_ROOT_PASSWORD
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
console.log('No MongoDB databases to provision.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
initService();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { MongoClient } from 'mongodb';
|
|
2
|
+
|
|
3
|
+
const retryInterval = 5000;
|
|
4
|
+
const maxRetries = 5;
|
|
5
|
+
|
|
6
|
+
let db;
|
|
7
|
+
|
|
8
|
+
export const mongoService = {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provision database and users
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} params - The parameters object
|
|
14
|
+
* @param {string} host - The database host
|
|
15
|
+
* @param {string} database - The database name
|
|
16
|
+
* @param {string} user - The database user
|
|
17
|
+
* @param {string} password - The database user password
|
|
18
|
+
* @param {string} rootPassword - The root user password
|
|
19
|
+
* @param {number} [port=27017] - The database port
|
|
20
|
+
*/
|
|
21
|
+
provisionDatabase: async ({host, database, user, password, rootPassword, port = 27017}) => {
|
|
22
|
+
|
|
23
|
+
const connectionUrl = `mongodb://root:${rootPassword}@${host}:${port}/admin`;
|
|
24
|
+
let retries = 0;
|
|
25
|
+
|
|
26
|
+
while (retries < maxRetries) {
|
|
27
|
+
try {
|
|
28
|
+
const dbClient = await MongoClient.connect(connectionUrl);
|
|
29
|
+
db = dbClient.db(database);
|
|
30
|
+
|
|
31
|
+
const existingUsers = await db.command({ usersInfo: 1 });
|
|
32
|
+
|
|
33
|
+
if (!existingUsers.users.some(u => u.user === user)) {
|
|
34
|
+
await db.command({
|
|
35
|
+
createUser: user,
|
|
36
|
+
pwd: password,
|
|
37
|
+
roles: [{ role: "readWrite", db: database }]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`Successfully provisioned MongoDB database: ${database} and user: ${user}`);
|
|
42
|
+
await dbClient.close();
|
|
43
|
+
return;
|
|
44
|
+
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`Failed provisioning Mongo database "${database}" for user "${user}" ": ${error.message}`);
|
|
47
|
+
retries++;
|
|
48
|
+
|
|
49
|
+
if (retries >= maxRetries) {
|
|
50
|
+
console.error(`Max retries reached. Could not provision database "${database}".`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
|
|
3
|
+
const retryInterval = 5000;
|
|
4
|
+
const maxRetries = 5;
|
|
5
|
+
|
|
6
|
+
export const mysqlService = {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provision database and users
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} params - The parameters object
|
|
12
|
+
* @param {string} host - The database host
|
|
13
|
+
* @param {string} database - The database name
|
|
14
|
+
* @param {string} user - The database user
|
|
15
|
+
* @param {string} password - The database user password
|
|
16
|
+
* @param {string} rootPassword - The root user password
|
|
17
|
+
*/
|
|
18
|
+
provisionDatabase: async ({host, database, user, password, rootPassword}) => {
|
|
19
|
+
let retries = 0;
|
|
20
|
+
|
|
21
|
+
while (retries < maxRetries) {
|
|
22
|
+
try {
|
|
23
|
+
const conn = await mysql.createConnection({
|
|
24
|
+
host: host || 'db-mysql',
|
|
25
|
+
user: 'root',
|
|
26
|
+
password: rootPassword
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await conn.query(`CREATE DATABASE IF NOT EXISTS ${mysql.escapeId(database)};`);
|
|
30
|
+
await conn.query(`CREATE USER IF NOT EXISTS ${mysql.escape(user)}@'%' IDENTIFIED BY ${mysql.escape(password)};`);
|
|
31
|
+
await conn.query(`GRANT ALL PRIVILEGES ON ${mysql.escapeId(database)}.* TO ${mysql.escape(user)}@'%';`);
|
|
32
|
+
await conn.query(`FLUSH PRIVILEGES;`);
|
|
33
|
+
|
|
34
|
+
console.log(`Successfully provisioned MySQL database: ${database} and user: ${user}`);
|
|
35
|
+
await conn.end();
|
|
36
|
+
return;
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(`Failed provisioning MySQL database "${database}" for user "${user}" ": ${error.message}`);
|
|
40
|
+
retries++;
|
|
41
|
+
|
|
42
|
+
if (retries >= maxRetries) {
|
|
43
|
+
console.error(`Max retries reached. Could not provision database "${database}".`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export const secrets = {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Collate MySQL databases from provisioner secrets
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} provisionerSecrets - The provisioner secrets object
|
|
9
|
+
* @returns {Object} - Collated MySQL databases
|
|
10
|
+
*/
|
|
11
|
+
collateMysqlDatabases: (provisionerSecrets) => {
|
|
12
|
+
|
|
13
|
+
const mysqlDatabases = {};
|
|
14
|
+
|
|
15
|
+
for (const [serviceKey, service] of Object.entries(provisionerSecrets.services)) {
|
|
16
|
+
for (const key of Object.keys(service)) {
|
|
17
|
+
if (key.startsWith('MYSQL_')) {
|
|
18
|
+
mysqlDatabases[serviceKey] = true;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const [key, value] of Object.entries(mysqlDatabases)) {
|
|
25
|
+
try {
|
|
26
|
+
mysqlDatabases[key] = {};
|
|
27
|
+
mysqlDatabases[key].host = provisionerSecrets.services[key].MYSQL_HOST;
|
|
28
|
+
mysqlDatabases[key].database = provisionerSecrets.services[key].MYSQL_DATABASE;
|
|
29
|
+
mysqlDatabases[key].user = provisionerSecrets.services[key].MYSQL_USER;
|
|
30
|
+
mysqlDatabases[key].password = provisionerSecrets.services[key].MYSQL_PASSWORD;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(`Missing database credentials for ${key} service. Please include: MYSQL_DATABASE, MYSQL_USER, and MYSQL_PASSWORD.`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!provisionerSecrets.provision.MYSQL_ROOT_PASSWORD) {
|
|
38
|
+
console.error('Missing MYSQL_ROOT_PASSWORD in provisioner secrets. Cannot provision databases.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return mysqlDatabases;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Collate MongoDB databases from provisioner secrets
|
|
47
|
+
*
|
|
48
|
+
* @param {Object} provisionerSecrets - The provisioner secrets object
|
|
49
|
+
* @returns {Object} - Collated MongoDB databases
|
|
50
|
+
*/
|
|
51
|
+
collateMongoDatabases: (provisionerSecrets) => {
|
|
52
|
+
|
|
53
|
+
const mongoDatabases = {};
|
|
54
|
+
|
|
55
|
+
for (const [serviceKey, service] of Object.entries(provisionerSecrets.services)) {
|
|
56
|
+
for (const key of Object.keys(service)) {
|
|
57
|
+
if (key.startsWith('MONGO_')) {
|
|
58
|
+
mongoDatabases[serviceKey] = true;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const [key, value] of Object.entries(mongoDatabases)) {
|
|
65
|
+
try {
|
|
66
|
+
mongoDatabases[key] = {};
|
|
67
|
+
mongoDatabases[key].host = provisionerSecrets.services[key].MONGO_HOST;
|
|
68
|
+
mongoDatabases[key].database = provisionerSecrets.services[key].MONGO_DATABASE;
|
|
69
|
+
mongoDatabases[key].user = provisionerSecrets.services[key].MONGO_USER;
|
|
70
|
+
mongoDatabases[key].password = provisionerSecrets.services[key].MONGO_PASSWORD;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Missing database credentials for ${key} service. Please include: MONGO_DATABASE, MONGO_USER, and MONGO_PASSWORD.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!provisionerSecrets.provision.MONGO_ROOT_PASSWORD) {
|
|
78
|
+
console.error('Missing MONGO_ROOT_PASSWORD in provisioner secrets. Cannot provision databases.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return mongoDatabases;
|
|
83
|
+
}
|
|
84
|
+
}
|