@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.
Files changed (259) hide show
  1. package/bootstrap/deploy.localdev.yml +44 -30
  2. package/bootstrap/secrets.localdev.yml +20 -14
  3. package/bootstrap/services/control/src/config.js +4 -0
  4. package/bootstrap/services/{agent → page}/Dockerfile +3 -3
  5. package/bootstrap/services/page/package.json +16 -0
  6. package/bootstrap/services/page/src/app.js +50 -0
  7. package/bootstrap/services/page/src/commands/block.handler.js +94 -0
  8. package/bootstrap/services/page/src/commands/page.handler.js +167 -0
  9. package/bootstrap/services/page/src/config.js +62 -0
  10. package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
  11. package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
  12. package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
  13. package/bootstrap/services/page/src/policies/block.policy.js +50 -0
  14. package/bootstrap/services/page/src/policies/page.policy.js +49 -0
  15. package/bootstrap/services/page/src/schema/page.schema.js +139 -0
  16. package/bootstrap/services/page/src/services/block.service.js +83 -0
  17. package/bootstrap/services/page/src/services/page.service.js +83 -0
  18. package/bootstrap/services/portal/Dockerfile +7 -10
  19. package/bootstrap/services/portal/README.md +66 -15
  20. package/bootstrap/services/portal/index.html +13 -0
  21. package/bootstrap/services/portal/nginx.conf +5 -12
  22. package/bootstrap/services/portal/package.json +27 -53
  23. package/bootstrap/services/portal/public/vite.svg +1 -0
  24. package/bootstrap/services/portal/react-router.config.js +7 -0
  25. package/bootstrap/services/portal/src/App.jsx +16 -0
  26. package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
  27. package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
  28. package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
  29. package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
  30. package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
  31. package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
  32. package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
  33. package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
  34. package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
  35. package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
  36. package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
  37. package/bootstrap/services/portal/src/assets/react.svg +1 -0
  38. package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
  39. package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
  40. package/bootstrap/services/portal/src/{ui/customSelect → components/CustomSelect}/CustomSelect.jsx +21 -3
  41. package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
  42. package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
  43. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
  44. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
  45. package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
  46. package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
  47. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
  48. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
  49. package/bootstrap/services/portal/src/css/style.css +446 -742
  50. package/bootstrap/services/portal/src/data/pages.data.js +10 -0
  51. package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
  52. package/bootstrap/services/portal/src/{ui/customSelect/customSelect.less → elements/CustomSelect/CustomSelect.less} +17 -7
  53. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
  54. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
  55. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
  56. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
  57. package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
  58. package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
  59. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
  60. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
  61. package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
  62. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
  63. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
  64. package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
  65. package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
  66. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
  67. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
  68. package/bootstrap/services/portal/src/main.jsx +51 -0
  69. package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
  70. package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
  71. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
  72. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
  73. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
  74. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
  75. package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
  76. package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
  77. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
  78. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
  79. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
  80. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
  81. package/bootstrap/services/portal/src/services/block.js +28 -0
  82. package/bootstrap/services/portal/src/services/client.js +67 -0
  83. package/bootstrap/services/portal/src/services/gravatar.js +14 -0
  84. package/bootstrap/services/portal/src/services/page.js +28 -0
  85. package/bootstrap/services/portal/src/services/storage.js +62 -0
  86. package/bootstrap/services/portal/src/services/user.js +41 -0
  87. package/bootstrap/services/portal/src/slices/authSlice.js +44 -14
  88. package/bootstrap/services/portal/src/store/configureStore.js +1 -3
  89. package/bootstrap/services/portal/src/style/cards.less +57 -0
  90. package/bootstrap/services/portal/src/{styles → style}/global.less +90 -45
  91. package/bootstrap/services/portal/src/style/icons.less +21 -0
  92. package/bootstrap/services/portal/src/style/inputs.less +52 -0
  93. package/bootstrap/services/portal/src/style/main.less +28 -0
  94. package/bootstrap/services/portal/src/utils/utils.js +9 -0
  95. package/bootstrap/services/portal/vite.config.js +12 -0
  96. package/bootstrap/services/user/Dockerfile +1 -1
  97. package/bootstrap/services/user/src/app.js +6 -1
  98. package/bootstrap/services/user/src/commands/user.handler.js +0 -3
  99. package/bootstrap/services/user/src/config.js +5 -1
  100. package/bootstrap/services/user/src/policies/user.policy.js +3 -1
  101. package/bootstrap/services/user/src/tests/commands/user.test.js +22 -0
  102. package/install-from-clone.sh +30 -0
  103. package/package.json +1 -1
  104. package/src/cli.js +8 -0
  105. package/src/config.js +2 -1
  106. package/src/dev/commands.js +10 -2
  107. package/src/dev/dev.service.js +167 -52
  108. package/src/provisioner/Dockerfile +27 -0
  109. package/src/provisioner/package.json +19 -0
  110. package/src/provisioner/src/app.js +56 -0
  111. package/src/provisioner/src/services/mongodb.js +58 -0
  112. package/src/provisioner/src/services/mysql.js +51 -0
  113. package/src/provisioner/src/services/secrets.js +84 -0
  114. package/src/scaffolder/commands.js +12 -5
  115. package/src/scaffolder/scaffolder.handler.js +256 -58
  116. package/templates/service/Dockerfile.hbs +4 -1
  117. package/templates/service/package.json.hbs +14 -16
  118. package/templates/service/src/app.js.hbs +53 -0
  119. package/templates/service/{commands → src/commands}/{{serviceName}}.handler.js.hbs +2 -3
  120. package/templates/service/src/mongodb.config.js.hbs +49 -0
  121. package/templates/service/{config.js.hbs → src/mysql.config.js.hbs} +10 -0
  122. package/{bootstrap/services/agent/src/schema/Agent.schema.js → templates/service/src/schema/{{serviceName}}.schema.js.hbs} +3 -3
  123. package/templates/service/src/services/mongodb.{{serviceName}}.service.js.hbs +70 -0
  124. package/bootstrap/services/agent/notes.md +0 -28
  125. package/bootstrap/services/agent/package.json +0 -16
  126. package/bootstrap/services/agent/src/app.js +0 -52
  127. package/bootstrap/services/agent/src/commands/agent.handler.js +0 -104
  128. package/bootstrap/services/agent/src/config.js +0 -52
  129. package/bootstrap/services/agent/src/controllers/http.controller.js +0 -44
  130. package/bootstrap/services/agent/src/controllers/message.controller.js +0 -51
  131. package/bootstrap/services/agent/src/db/migrations/01-init.js +0 -50
  132. package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +0 -36
  133. package/bootstrap/services/agent/src/policies/agent.policy.js +0 -13
  134. package/bootstrap/services/agent/src/services/agent.service.js +0 -259
  135. package/bootstrap/services/agent/src/services/chatgpt.service.js +0 -46
  136. package/bootstrap/services/agent/src/services/manifest.service.js +0 -21
  137. package/bootstrap/services/portal/Dockerfile.remote +0 -40
  138. package/bootstrap/services/portal/public/favicon.ico +0 -0
  139. package/bootstrap/services/portal/public/gnar-white.png +0 -0
  140. package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
  141. package/bootstrap/services/portal/public/index.html +0 -43
  142. package/bootstrap/services/portal/public/logo192.png +0 -0
  143. package/bootstrap/services/portal/public/logo512.png +0 -0
  144. package/bootstrap/services/portal/public/manifest.json +0 -25
  145. package/bootstrap/services/portal/public/robots.txt +0 -3
  146. package/bootstrap/services/portal/src/App.js +0 -56
  147. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +0 -1
  148. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +0 -1
  149. package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +0 -1
  150. package/bootstrap/services/portal/src/assets/activity.svg +0 -3
  151. package/bootstrap/services/portal/src/assets/arrow.svg +0 -3
  152. package/bootstrap/services/portal/src/assets/bin-white.svg +0 -3
  153. package/bootstrap/services/portal/src/assets/bin.svg +0 -3
  154. package/bootstrap/services/portal/src/assets/check.svg +0 -3
  155. package/bootstrap/services/portal/src/assets/chevron.svg +0 -3
  156. package/bootstrap/services/portal/src/assets/contact.svg +0 -3
  157. package/bootstrap/services/portal/src/assets/dots-vertical.svg +0 -5
  158. package/bootstrap/services/portal/src/assets/eye-off.svg +0 -3
  159. package/bootstrap/services/portal/src/assets/eye.svg +0 -4
  160. package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +0 -47
  161. package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +0 -47
  162. package/bootstrap/services/portal/src/assets/gnar_engine.svg +0 -3
  163. package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
  164. package/bootstrap/services/portal/src/assets/home.svg +0 -3
  165. package/bootstrap/services/portal/src/assets/link.svg +0 -3
  166. package/bootstrap/services/portal/src/assets/lock.svg +0 -3
  167. package/bootstrap/services/portal/src/assets/package.svg +0 -4
  168. package/bootstrap/services/portal/src/assets/raffle.svg +0 -3
  169. package/bootstrap/services/portal/src/assets/settings.svg +0 -4
  170. package/bootstrap/services/portal/src/assets/shopping-bag.svg +0 -3
  171. package/bootstrap/services/portal/src/assets/user-black.svg +0 -3
  172. package/bootstrap/services/portal/src/assets/user.svg +0 -3
  173. package/bootstrap/services/portal/src/assets/users.svg +0 -3
  174. package/bootstrap/services/portal/src/assets/wallet.svg +0 -3
  175. package/bootstrap/services/portal/src/data/data.js +0 -70
  176. package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +0 -32
  177. package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +0 -160
  178. package/bootstrap/services/portal/src/features/crud/crudEdit.less +0 -230
  179. package/bootstrap/services/portal/src/features/crud/crudList.less +0 -134
  180. package/bootstrap/services/portal/src/features/crud/crudPage.less +0 -31
  181. package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +0 -108
  182. package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +0 -243
  183. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +0 -109
  184. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +0 -315
  185. package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +0 -104
  186. package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +0 -388
  187. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +0 -104
  188. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +0 -208
  189. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +0 -110
  190. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +0 -261
  191. package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +0 -107
  192. package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +0 -402
  193. package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +0 -30
  194. package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +0 -113
  195. package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +0 -56
  196. package/bootstrap/services/portal/src/features/loginForm/loginForm.less +0 -56
  197. package/bootstrap/services/portal/src/features/notes/Notes.jsx +0 -18
  198. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +0 -96
  199. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +0 -74
  200. package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +0 -102
  201. package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +0 -24
  202. package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +0 -99
  203. package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +0 -46
  204. package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +0 -64
  205. package/bootstrap/services/portal/src/features/sidebar/sidebar.less +0 -49
  206. package/bootstrap/services/portal/src/features/skus/Skus.jsx +0 -109
  207. package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +0 -44
  208. package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +0 -32
  209. package/bootstrap/services/portal/src/features/user/User.jsx +0 -54
  210. package/bootstrap/services/portal/src/features/user/user.less +0 -57
  211. package/bootstrap/services/portal/src/includes/utilities.js +0 -259
  212. package/bootstrap/services/portal/src/index.js +0 -14
  213. package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +0 -50
  214. package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +0 -17
  215. package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +0 -48
  216. package/bootstrap/services/portal/src/layouts/loginLayout.less +0 -33
  217. package/bootstrap/services/portal/src/layouts/portalLayout.less +0 -67
  218. package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +0 -199
  219. package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +0 -17
  220. package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +0 -10
  221. package/bootstrap/services/portal/src/pages/login/Login.jsx +0 -15
  222. package/bootstrap/services/portal/src/pages/login/login.less +0 -10
  223. package/bootstrap/services/portal/src/pages/orders/Orders.jsx +0 -199
  224. package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +0 -15
  225. package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +0 -15
  226. package/bootstrap/services/portal/src/pages/payments/Payments.jsx +0 -10
  227. package/bootstrap/services/portal/src/pages/portal/Portal.jsx +0 -43
  228. package/bootstrap/services/portal/src/pages/products/Products.jsx +0 -212
  229. package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +0 -124
  230. package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +0 -186
  231. package/bootstrap/services/portal/src/pages/reports/Reports.jsx +0 -10
  232. package/bootstrap/services/portal/src/pages/settings/Settings.jsx +0 -10
  233. package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +0 -199
  234. package/bootstrap/services/portal/src/pages/users/Users.jsx +0 -193
  235. package/bootstrap/services/portal/src/pages/users/users.less +0 -25
  236. package/bootstrap/services/portal/src/styles/inputs.less +0 -157
  237. package/bootstrap/services/portal/src/styles/main.less +0 -26
  238. package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +0 -97
  239. package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +0 -23
  240. package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +0 -17
  241. package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +0 -42
  242. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +0 -63
  243. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +0 -63
  244. package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +0 -19
  245. package/bootstrap/services/portal/src/ui/loader/Loader.jsx +0 -12
  246. package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +0 -23
  247. package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +0 -29
  248. package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +0 -69
  249. package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +0 -27
  250. package/bootstrap/services/user/src/tests/user.test.js +0 -126
  251. package/templates/service/app.js.hbs +0 -38
  252. package/templates/service/schema/{{serviceName}}.schema.js.hbs +0 -14
  253. /package/bootstrap/services/portal/src/{ui/saveButton/saveButton.less → components/CustomSelect/CustomSelect.less} +0 -0
  254. /package/templates/service/{controllers → src/controllers}/http.controller.js.hbs +0 -0
  255. /package/templates/service/{controllers → src/controllers}/message.controller.js.hbs +0 -0
  256. /package/templates/service/{db → src/mysql.db}/migrations/01-init.js.hbs +0 -0
  257. /package/templates/service/{db → src/mysql.db}/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +0 -0
  258. /package/templates/service/{policies → src/policies}/{{serviceName}}.policy.js.hbs +0 -0
  259. /package/templates/service/{services/{{serviceName}}.service.js.hbs → src/services/mysql.{{serviceName}}.service.js.hbs} +0 -0
@@ -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);
@@ -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} outputPath - where to write nginx.conf
146
+ * @param {string} serviceConfDir
147
+ * @param {string} projectDir
125
148
  */
126
- export async function createDynamicNginxConf({ config, outputPath }) {
149
+ export async function createDynamicNginxConf({ config, serviceConfDir, projectDir }) {
127
150
  // Start with the static parts of nginx.conf
128
151
  let nginxConf = `
129
- http {
130
- server {
131
- listen 80;
132
- server_name ${config.namespace};
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 coreDeve mode
341
+ // add the core source code mount if in coreDev mode
243
342
  if (coreDev) {
244
- services[`${svc.name}-service`].volumes.push(`../../../gnar-engine-core:${gnarEngineCliConfig.corePath}`);
343
+ services[`${svc.name}-service`].volumes.push(`../../../core/:${gnarEngineCliConfig.corePath}`);
245
344
  }
246
345
 
247
- // add a mysql instance if required
346
+ // check if mysql service required
248
347
  if (
249
348
  serviceEnvVars.MYSQL_HOST &&
250
- serviceEnvVars.MYSQL_DATABASE &&
251
- serviceEnvVars.MYSQL_USER &&
252
- serviceEnvVars.MYSQL_PASSWORD &&
253
- serviceEnvVars.MYSQL_RANDOM_ROOT_PASSWORD
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
- services[`${svc.name}-db`] = {
256
- container_name: `ge-${config.environment}-${config.namespace}-${svc.name}-db`,
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: serviceEnvVars.MYSQL_HOST,
264
- MYSQL_DATABASE: serviceEnvVars.MYSQL_DATABASE,
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/${svc.name}-db-data:/var/lib/mysql`
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
- // add a mongodb instance if required
279
- if (
280
- serviceEnvVars.MONGO_URL &&
281
- serviceEnvVars.MONGO_ROOT_PASSWORD &&
282
- serviceEnvVars.MONGO_USER &&
283
- serviceEnvVars.MONGO_PASSWORD
284
- ) {
285
- services[`${svc.name}-mongo`] = {
286
- container_name: `ge-${config.environment}-${config.namespace}-${svc.name}-mongo`,
287
- image: `ge-${config.environment}-${config.namespace}-${svc.name}-mongo`,
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}:${mongoPortsCounter}`
404
+ `${mongoPortsCounter}:27017`
290
405
  ],
291
406
  restart: 'always',
292
407
  environment: {
293
- [`${svc.name.toUpperCase()}_MONGO_URL`]: serviceEnvVars.MONGO_URL,
294
- [`${svc.name.toUpperCase()}_MONGO_ROOT_PASSWORD`]: serviceEnvVars.MONGO_ROOT_PASSWORD,
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}/Data/${svc.name}-mongo-data:/data/db`
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
+ }