From 93ae6b8c19e1cb0505a76d94bbdf26867b84bf7a Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 00:28:03 +0200 Subject: [PATCH 01/29] refactor: organize files by features --- package-lock.json | 86 ++++++- package.json | 1 + src/features/thunderstorm/database.ts | 79 +++++++ src/features/thunderstorm/index.ts | 312 ++++---------------------- src/features/thunderstorm/project.ts | 33 +++ src/features/thunderstorm/storage.ts | 108 +++++++++ src/features/thunderstorm/swa.ts | 55 +++++ 7 files changed, 395 insertions(+), 279 deletions(-) create mode 100644 src/features/thunderstorm/database.ts create mode 100644 src/features/thunderstorm/project.ts create mode 100644 src/features/thunderstorm/storage.ts create mode 100644 src/features/thunderstorm/swa.ts diff --git a/package-lock.json b/package-lock.json index 0ea3079..c0f804a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2094,6 +2094,20 @@ "@types/node": "*" } }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -2534,8 +2548,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "before-after-hook": { "version": "2.2.1", @@ -2715,6 +2728,14 @@ "node-int64": "^0.4.0" } }, + "bson": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.4.1.tgz", + "integrity": "sha512-Uu4OCZa0jouQJCKOk1EmmyqtdWAP5HVLru4lQxTwzJzxT+sJ13lVpEZU/MATDxtHiekWMAL84oQY3Xn1LpJVSg==", + "requires": { + "buffer": "^5.6.0" + } + }, "btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", @@ -2724,7 +2745,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3964,6 +3984,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" + }, "deprecated-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/deprecated-obj/-/deprecated-obj-2.0.0.tgz", @@ -5176,8 +5201,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "5.1.8", @@ -7857,6 +7881,12 @@ "object-visit": "^1.0.0" } }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, "meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -8051,6 +8081,26 @@ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true }, + "mongodb": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.0.tgz", + "integrity": "sha512-Gx9U9MsFWgJ3E0v4oHAdWvYTGBznNYPCkhmD/3i/kPTY/URnPfHD5/6VoKUFrdgQTK3icFiM9976hVbqCRBO9Q==", + "requires": { + "bson": "^4.4.0", + "denque": "^1.5.0", + "mongodb-connection-string-url": "^1.0.1", + "saslprep": "^1.0.0" + } + }, + "mongodb-connection-string-url": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-1.1.0.tgz", + "integrity": "sha512-g0Qaj4AzIaktWKBkfjMjwzvBzZQN1mtb2DVOTbjdvlaqTa5lGLcnTeh0/9R9mPiIt2lvRGOrDgUdazeP5rD9oA==", + "requires": { + "@types/whatwg-url": "^8.0.0", + "whatwg-url": "^8.4.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -8854,8 +8904,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "pupa": { "version": "2.1.1", @@ -9891,6 +9940,15 @@ } } }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -10194,6 +10252,15 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -10617,7 +10684,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, "requires": { "punycode": "^2.1.1" } @@ -11024,8 +11090,7 @@ "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "whatwg-encoding": { "version": "1.0.5", @@ -11046,7 +11111,6 @@ "version": "8.7.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, "requires": { "lodash": "^4.7.0", "tr46": "^2.1.0", diff --git a/package.json b/package.json index 8a5701f..4ce7d3a 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "inquirer": "^7.0.0", "internal-ip": "^6.2.0", "jmespath": "^0.15.0", + "mongodb": "^4.1.0", "ora": "^3.4.0", "rimraf": "^3.0.2", "shelljs": "^0.8.4", diff --git a/src/features/thunderstorm/database.ts b/src/features/thunderstorm/database.ts new file mode 100644 index 0000000..910b7cc --- /dev/null +++ b/src/features/thunderstorm/database.ts @@ -0,0 +1,79 @@ + +import chalk from "chalk"; +import { MongoClient } from "mongodb"; +import { sendWebSocketResponse } from "."; +import { az } from "../../core/utils"; + +export async function createDatabase({ ws, requestId, projectName, projectNameUnique }: any) { + try { + + sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + }, 202); + + // TODO: enable free tier for non-Internal subscriptions + // --enable-free-tier \ + + await az( + `cosmosdb create \ + --name "${projectNameUnique}" \ + --resource-group "${projectName}" \ + --kind "MongoDB" \ + --server-version "4.0" \ + --default-consistency-level "Eventual" \ + --tag "x-created-by=hexa" \ + --enable-multiple-write-locations false \ + --enable-automatic-failover false \ + --query "{id: id, name: name, tags: tags, endpoint: documentEndpoint}"` + ); + + await az( + `cosmosdb mongodb database create \ + --name "${projectNameUnique}" \ + --account-name "${projectNameUnique}" \ + --resource-group "${projectName}"` + ); + + // fetch connection strings + const connectionStrings = await getDatabaseConnectionString({ + projectNameUnique, + projectName, + }); + + sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + connectionString: connectionStrings.connectionStrings[0].connectionString, + }, 200); + + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + error + }, 500); + + } + +} + +async function getDatabaseConnectionString({ projectNameUnique, projectName }: { projectNameUnique: string, projectName: string }) { + return await az<{ connectionStrings: Array<{ connectionString: string, description: string }> }>( + `cosmosdb keys list \ + --name "${projectNameUnique}" \ + --resource-group "${projectName}" \ + --type "connection-strings"` + ); +} + +export async function getDatabaseCollection({ projectNameUnique, cosmosdbConnectionString }: { projectNameUnique: string, cosmosdbConnectionString: string }) { + const client = await MongoClient.connect(cosmosdbConnectionString); + const database = client.db(projectNameUnique); + return database.listCollections(); +} + +export async function getDatabaseDocument({ projectNameUnique, cosmosdbConnectionString, collectionName }: { projectNameUnique: string, cosmosdbConnectionString: string, collectionName: string }) { + const client = await MongoClient.connect(cosmosdbConnectionString); + const database = client.db(projectNameUnique); + return database.collection(collectionName).find(); +} diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index fea7445..23b9405 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -1,18 +1,13 @@ import WebSocket from 'ws'; - import chalk from 'chalk'; import util from 'util'; import { az } from '../../core/utils'; import createGitHubRepo from '../github/repo'; import { loginWithGitHub } from '../github/login-github'; - -type AzureBlobStorageItem = { - name: string, - contentLength: number, - contentType: string, - lastModified: string; - url: string; -}; +import { createDatabase } from './database'; +import { createProject } from './project'; +import { createStorage, listStorage } from './storage'; +import { createSwa, updateSwaWithDatabaseConnectionStrings } from './swa'; type WsRequest = { method: 'LOGINAZURE' | 'LOGINGITHUB' | 'GET' | 'POST' | 'DELETE' | 'PUT' | 'STATUS'; @@ -54,21 +49,31 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. const request = JSON.parse(String(message)); let { method, url = '/', requestId, body } = request as WsRequest; - // /accounts/${subscriptionId}/projects/${resourceId}/storages/${storageId} - const [_, _subscriptionLabel, subscriptionId, resourceType, resourceId, providerType, storageId] = url.split('/'); - + // get project name from body (only sent when creating a project) let projectName = (body?.projectName)?.replace(/\s+/g, ''); - // some resources require that name values must be less than 24 characters with no whitespace - let projectNameUnique = `${projectName}${(Math.random() + 1).toString(36).substring(2)}`.substr(0, 24); - - // if resourceId is provided then use it as project name - if (resourceId) { - projectName = resourceId; + // extract request metadata from URL + // /accounts/${accountId}/projects/${projectId}/{storage,database}/${providerId} + const [ + _, + _accountsLabel, + accountId, + projectType, + projectId, + providerType, + providerId + ] = url.split('/'); + + // if projectId is provided then use it as project name (this means the project alreadt exists) + if (projectId) { + projectName = projectId; } - if (storageId) { - projectNameUnique = storageId; + // some resources require that name values must be less than 24 characters with no whitespace + let projectNameUnique = `${projectName + (Math.random() + 1).toString(36).substring(2)}`.substr(0, 24); + + if (providerType && providerId) { + projectNameUnique = providerId; } const location = 'westeurope'; @@ -77,11 +82,20 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. console.log(`Request:`); console.log(request); console.log(`Parameters:`); - console.log({ projectName, projectNameUnique, location, subscriptionId, resourceType, resourceId, providerType }); + console.log({ + projectName, + projectNameUnique, + location, + accountId, + projectType, + projectId, + providerType, + providerId + }); switch (method) { case 'POST': - if (resourceType === 'projects') { + if (projectType === 'projects') { try { sendWebSocketResponse(ws, requestId, { @@ -139,7 +153,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. requestId, projectName, projectNameUnique, - subscriptionId, + accountId, location }); @@ -148,7 +162,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. requestId, projectName, projectNameUnique, - subscriptionId + accountId }); await Promise.all([swa, storage, databse]); @@ -179,9 +193,9 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. case 'GET': - if (resourceType === 'projects') { + if (projectType === 'projects') { // GET /accounts/${accountId}/projects/${projectId} - if (resourceId) { + if (projectId) { if (!providerType) { // TODO: list all resource groups } @@ -199,14 +213,14 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. else { sendWebSocketResponse(ws, requestId, null, 202); let resourceGroupsList = await az( - `group list --subscription "${subscriptionId}" --query "[].{name:name, id:id, location:location, tags:tags}"` + `group list --subscription "${accountId}" --query "[].{name:name, id:id, location:location, tags:tags}"` ); resourceGroupsList = resourceGroupsList.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "hexa")); sendWebSocketResponse(ws, requestId, resourceGroupsList); } } else { - sendWebSocketResponse(ws, requestId, { error: `resorce type not implemented "${resourceType}"` }, 501); + sendWebSocketResponse(ws, requestId, { error: `resorce type not implemented "${projectType}"` }, 501); } break; @@ -239,14 +253,14 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. break; case 'DELETE': - if (resourceType === 'projects') { + if (projectType === 'projects') { try { sendWebSocketResponse(ws, requestId, null, 202); await az( `group delete \ --name "${projectName}" \ - --subscription "${subscriptionId}" \ + --subscription "${accountId}" \ --yes` ); sendWebSocketResponse(ws, requestId, null, 200); @@ -263,241 +277,3 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. sendWebSocketResponse(ws, requestId, { error: 'method not allowed' }, 405); } } - -async function createProject({ ws, requestId, projectName, projectNameUnique, location }: any) { - - try { - sendWebSocketResponse(ws, requestId, { - resource: 'PROJECT' - }, 202); - - await az( - `group create \ - --location "${location}" \ - --name "${projectName}" \ - --tag "x-created-by=hexa" \ - --tag "x-project-name="${projectNameUnique}" \ - --query "{name:name, id:id, location:location}"` - ); - - sendWebSocketResponse(ws, requestId, { - resource: 'PROJECT', - }, 200); - - } catch (error) { - console.error(chalk.red(error)); - - return sendWebSocketResponse(ws, requestId, { - resource: 'PROJECT', - error - }, 500); - } -} - -async function createSwa({ ws, requestId, projectName, projectNameUnique, location, html_url, default_branch, gitHubToken }: any) { - - try { - sendWebSocketResponse(ws, requestId, { - resource: 'SWA' - }, 202); - - const swa = await az( - `staticwebapp create \ - --name "${projectNameUnique}" \ - --resource-group "${projectName}" \ - --source "${html_url}" \ - --location "${location}" \ - --branch "${default_branch}" \ - --output-location "./" \ - --api-location "api" \ - --app-location "./" \ - --token "${gitHubToken}" \ - --tag "x-created-by=hexa" \ - --sku "free" \ - --debug \ - --query "{name:name, id:id, url:defaultHostname}"`, - ); - - sendWebSocketResponse(ws, requestId, { - resource: 'SWA', - url: swa.url - }, 200); - - } catch (error) { - console.error(chalk.red(error)); - - return sendWebSocketResponse(ws, requestId, { - resource: 'SWA', - error - }, 500); - } -} - -async function getStorageConnectionString({ projectNameUnique }: any) { - return await az<{ connectionString: string }>( - `storage account show-connection-string \ - --name "${projectNameUnique}" - ` - ); -} - -async function createStorage({ ws, location, requestId, projectName, projectNameUnique, subscriptionId }: any) { - - try { - sendWebSocketResponse(ws, requestId, { - resource: 'STORAGE', - }, 202); - - await az( - `storage account create \ - --location "${location}" \ - --name "${projectNameUnique}" \ - --subscription "${subscriptionId}" \ - --resource-group "${projectName}" \ - --kind "StorageV2" \ - --tag "x-created-by=hexa" \ - --query "{name:name, id:id, location:location}"` - ); - - const storageConnectionString = await getStorageConnectionString({ - projectNameUnique - }); - - KeyVault.ConnectionString.Storage = storageConnectionString.connectionString; - - await az( - `storage container create \ - --resource-group "${projectName}" \ - --name "${projectNameUnique}" \ - --public-access "blob" \ - --connection-string "${storageConnectionString.connectionString}"` - ); - - sendWebSocketResponse(ws, requestId, { - resource: 'STORAGE', - }, 200); - - } catch (error) { - console.error(chalk.red(error)); - - return sendWebSocketResponse(ws, requestId, { - resource: 'STORAGE', - error - }, 500); - - } -} - -async function createDatabase({ ws, requestId, projectName, projectNameUnique }: any) { - try { - - sendWebSocketResponse(ws, requestId, { - resource: 'DATABASE', - }, 202); - - // TODO: enable free tier for non-Internal subscriptions - // --enable-free-tier \ - - await az( - `cosmosdb create \ - --name "${projectNameUnique}" \ - --resource-group "${projectName}" \ - --kind "MongoDB" \ - --server-version "4.0" \ - --default-consistency-level "Eventual" \ - --tag "x-created-by=hexa" \ - --enable-multiple-write-locations false \ - --enable-automatic-failover false \ - --query "{id: id, name: name, tags: tags, endpoint: documentEndpoint}"` - ); - - await az( - `cosmosdb mongodb database create \ - --name "${projectNameUnique}" \ - --account-name "${projectNameUnique}" \ - --resource-group "${projectName}"` - ); - - // fetch connection strings - const connectionStrings = await az<{ connectionStrings: Array<{ connectionString: string, description: string }> }>( - `cosmosdb keys list \ - --name "${projectNameUnique}" \ - --resource-group "${projectName}" \ - --type "connection-strings"` - ); - - KeyVault.ConnectionString.Database = connectionStrings.connectionStrings.pop()?.connectionString; - - sendWebSocketResponse(ws, requestId, { - resource: 'DATABASE', - connectionString: KeyVault.ConnectionString.Database - }, 200); - - - } catch (error) { - console.error(chalk.red(error)); - - return sendWebSocketResponse(ws, requestId, { - resource: 'DATABASE', - error - }, 500); - - } - -} - -async function updateSwaWithDatabaseConnectionStrings({ projectNameUnique, databaseConnectionString }: any) { - console.log(`TODO: az staticwebapp appsettings set doesn not support updating app setting right now!`); - console.log({ projectNameUnique, databaseConnectionString }); - return Promise.resolve(); - - // return await az( - // `staticwebapp appsettings set \ - // --name "${projectNameUnique}" \ - // --setting-names 'COSMOSDB_CONNECTION_STRING="${databaseConnectionString}"'` - // ); -} - -async function listStorage({ ws, requestId, projectName, projectNameUnique }: any) { - try { - - sendWebSocketResponse(ws, requestId, { - resource: 'STORAGE', - }, 202); - - const storageConnectionString = await getStorageConnectionString({ - projectName, - projectNameUnique - }); - - let blobs = await az>( - `storage blob list \ - --account-name "${projectNameUnique}" \ - --container-name "${projectNameUnique}" \ - --connection-string "${storageConnectionString.connectionString}" \ - --query "[].{name: name, contentLength: properties.contentLength, lastModified: properties.lastModified, contentType: properties.contentSettings.contentType}" - `); - - blobs = blobs.map((blob: AzureBlobStorageItem) => { - return { - ...blob, - url: `https://${projectNameUnique}.blob.core.windows.net/${projectNameUnique}/${blob.name}` - } - }); - - return sendWebSocketResponse(ws, requestId, { - resource: 'STORAGE', - blobs - }, 200); - - } - catch (error) { - console.error(chalk.red(error)); - - return sendWebSocketResponse(ws, requestId, { - resource: 'STORAGE', - error - }, 500); - - } -} diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts new file mode 100644 index 0000000..96ad01a --- /dev/null +++ b/src/features/thunderstorm/project.ts @@ -0,0 +1,33 @@ +import chalk from "chalk"; +import { sendWebSocketResponse } from "."; +import { az } from "../../core/utils"; + +export async function createProject({ ws, requestId, projectName, projectNameUnique, location }: any) { + + try { + sendWebSocketResponse(ws, requestId, { + resource: 'PROJECT' + }, 202); + + await az( + `group create \ + --location "${location}" \ + --name "${projectName}" \ + --tag "x-created-by=hexa" \ + --tag "x-project-name="${projectNameUnique}" \ + --query "{name:name, id:id, location:location}"` + ); + + sendWebSocketResponse(ws, requestId, { + resource: 'PROJECT', + }, 200); + + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'PROJECT', + error + }, 500); + } +} diff --git a/src/features/thunderstorm/storage.ts b/src/features/thunderstorm/storage.ts new file mode 100644 index 0000000..8b50ece --- /dev/null +++ b/src/features/thunderstorm/storage.ts @@ -0,0 +1,108 @@ +import chalk from "chalk"; +import { sendWebSocketResponse } from "."; +import { az } from "../../core/utils"; + +type AzureBlobStorageItem = { + name: string, + contentLength: number, + contentType: string, + lastModified: string; + url: string; +}; + +export async function getStorageConnectionString({ projectNameUnique }: any) { + return await az<{ connectionString: string }>( + `storage account show-connection-string \ + --name "${projectNameUnique}" + ` + ); +} + +export async function createStorage({ ws, location, requestId, projectName, projectNameUnique, subscriptionId }: any) { + + try { + sendWebSocketResponse(ws, requestId, { + resource: 'STORAGE', + }, 202); + + await az( + `storage account create \ + --location "${location}" \ + --name "${projectNameUnique}" \ + --subscription "${subscriptionId}" \ + --resource-group "${projectName}" \ + --kind "StorageV2" \ + --tag "x-created-by=hexa" \ + --query "{name:name, id:id, location:location}"` + ); + + const storageConnectionString = await getStorageConnectionString({ + projectNameUnique + }); + + await az( + `storage container create \ + --resource-group "${projectName}" \ + --name "${projectNameUnique}" \ + --public-access "blob" \ + --connection-string "${storageConnectionString.connectionString}"` + ); + + sendWebSocketResponse(ws, requestId, { + resource: 'STORAGE', + }, 200); + + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'STORAGE', + error + }, 500); + + } +} + +export async function listStorage({ ws, requestId, projectName, projectNameUnique }: any) { + try { + + sendWebSocketResponse(ws, requestId, { + resource: 'STORAGE', + }, 202); + + const storageConnectionString = await getStorageConnectionString({ + projectName, + projectNameUnique + }); + + let blobs = await az>( + `storage blob list \ + --account-name "${projectNameUnique}" \ + --container-name "${projectNameUnique}" \ + --connection-string "${storageConnectionString.connectionString}" \ + --query "[].{name: name, contentLength: properties.contentLength, lastModified: properties.lastModified, contentType: properties.contentSettings.contentType}" + `); + + blobs = blobs.map((blob: AzureBlobStorageItem) => { + return { + ...blob, + url: `https://${projectNameUnique}.blob.core.windows.net/${projectNameUnique}/${blob.name}` + } + }); + + return sendWebSocketResponse(ws, requestId, { + resource: 'STORAGE', + blobs + }, 200); + + } + catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'STORAGE', + error + }, 500); + + } +} diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts new file mode 100644 index 0000000..4d9985d --- /dev/null +++ b/src/features/thunderstorm/swa.ts @@ -0,0 +1,55 @@ +import chalk from "chalk"; +import { sendWebSocketResponse } from "."; +import { az } from "../../core/utils"; + + +export async function createSwa({ ws, requestId, projectName, projectNameUnique, location, html_url, default_branch, gitHubToken }: any) { + + try { + sendWebSocketResponse(ws, requestId, { + resource: 'SWA' + }, 202); + + const swa = await az( + `staticwebapp create \ + --name "${projectNameUnique}" \ + --resource-group "${projectName}" \ + --source "${html_url}" \ + --location "${location}" \ + --branch "${default_branch}" \ + --output-location "./" \ + --api-location "api" \ + --app-location "./" \ + --token "${gitHubToken}" \ + --tag "x-created-by=hexa" \ + --sku "free" \ + --debug \ + --query "{name:name, id:id, url:defaultHostname}"`, + ); + + sendWebSocketResponse(ws, requestId, { + resource: 'SWA', + url: swa.url + }, 200); + + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'SWA', + error + }, 500); + } +} + +export async function updateSwaWithDatabaseConnectionStrings({ projectNameUnique, databaseConnectionString }: any) { + console.log(`TODO: az staticwebapp appsettings set doesn not support updating app setting right now!`); + console.log({ projectNameUnique, databaseConnectionString }); + return Promise.resolve(); + + // return await az( + // `staticwebapp appsettings set \ + // --name "${projectNameUnique}" \ + // --setting-names 'COSMOSDB_CONNECTION_STRING="${databaseConnectionString}"'` + // ); +} From c269158fab0a9101c864698fb224c335ae7e170f Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 01:17:02 +0200 Subject: [PATCH 02/29] fix: handle errors when listing projects --- src/features/thunderstorm/index.ts | 22 ++++++++++++---------- src/features/thunderstorm/project.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 23b9405..46d1a99 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -5,7 +5,7 @@ import { az } from '../../core/utils'; import createGitHubRepo from '../github/repo'; import { loginWithGitHub } from '../github/login-github'; import { createDatabase } from './database'; -import { createProject } from './project'; +import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; import { createSwa, updateSwaWithDatabaseConnectionStrings } from './swa'; @@ -51,6 +51,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. // get project name from body (only sent when creating a project) let projectName = (body?.projectName)?.replace(/\s+/g, ''); + let projectNameUnique = undefined; // extract request metadata from URL // /accounts/${accountId}/projects/${projectId}/{storage,database}/${providerId} @@ -69,8 +70,10 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. projectName = projectId; } - // some resources require that name values must be less than 24 characters with no whitespace - let projectNameUnique = `${projectName + (Math.random() + 1).toString(36).substring(2)}`.substr(0, 24); + if (projectName) { + // some resources require that name values must be less than 24 characters with no whitespace + projectNameUnique = `${projectName + (Math.random() + 1).toString(36).substring(2)}`.substr(0, 24); + } if (providerType && providerId) { projectNameUnique = providerId; @@ -211,12 +214,11 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. } // GET /accounts/${accountId}/projects/ else { - sendWebSocketResponse(ws, requestId, null, 202); - let resourceGroupsList = await az( - `group list --subscription "${accountId}" --query "[].{name:name, id:id, location:location, tags:tags}"` - ); - resourceGroupsList = resourceGroupsList.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "hexa")); - sendWebSocketResponse(ws, requestId, resourceGroupsList); + await listProjects({ + ws, + requestId, + accountId + }); } } else { @@ -274,6 +276,6 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. break; default: - sendWebSocketResponse(ws, requestId, { error: 'method not allowed' }, 405); + sendWebSocketResponse(ws, requestId, { error: `method "${method}" not allowed` }, 405); } } diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index 96ad01a..ab22721 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -31,3 +31,29 @@ export async function createProject({ ws, requestId, projectName, projectNameUni }, 500); } } + +export async function listProjects({ ws, requestId, accountId }: any) { + + try { + + sendWebSocketResponse(ws, requestId, { + resource: 'PROJECT', + }, 202); + + let resourceGroupsList = await az( + `group list --subscription "${accountId}" --query "[].{name:name, id:id, location:location, tags:tags}"` + ); + resourceGroupsList = resourceGroupsList.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "hexa")); + sendWebSocketResponse(ws, requestId, { + projects: resourceGroupsList + }, 200); + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'PROJECT', + error + }, 500); + } + +} From 8e7cfacae0fa1c2f13e52ae7c13689ce9cdcbf25 Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Thu, 5 Aug 2021 03:25:57 +0200 Subject: [PATCH 03/29] feat: add method to retrieve collections with documents (#23) --- src/features/thunderstorm/database.ts | 41 +++++++++++++++++++++------ src/features/thunderstorm/index.ts | 13 +++++++-- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/features/thunderstorm/database.ts b/src/features/thunderstorm/database.ts index 910b7cc..d621f5e 100644 --- a/src/features/thunderstorm/database.ts +++ b/src/features/thunderstorm/database.ts @@ -66,14 +66,37 @@ async function getDatabaseConnectionString({ projectNameUnique, projectName }: { ); } -export async function getDatabaseCollection({ projectNameUnique, cosmosdbConnectionString }: { projectNameUnique: string, cosmosdbConnectionString: string }) { - const client = await MongoClient.connect(cosmosdbConnectionString); - const database = client.db(projectNameUnique); - return database.listCollections(); -} +export async function getDatabase({ ws, requestId, projectName, projectNameUnique }: any) { + try { + sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + }, 202); + + const cosmosdbConnectionString = await getDatabaseConnectionString({ + projectName, + projectNameUnique + }); + + const client = await MongoClient.connect(cosmosdbConnectionString.connectionStrings[0].connectionString); + const database = client.db(projectNameUnique); + + const collections = await database.listCollections().toArray() as any; + + for (const collection of collections) { + collection.documents = await database.collection(collection.name).find().toArray(); + } + + return sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + collections + }, 200); + + } catch (error) { + console.error(chalk.red(error)); -export async function getDatabaseDocument({ projectNameUnique, cosmosdbConnectionString, collectionName }: { projectNameUnique: string, cosmosdbConnectionString: string, collectionName: string }) { - const client = await MongoClient.connect(cosmosdbConnectionString); - const database = client.db(projectNameUnique); - return database.collection(collectionName).find(); + return sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + error + }, 500); + } } diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 46d1a99..0c99417 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -4,7 +4,7 @@ import util from 'util'; import { az } from '../../core/utils'; import createGitHubRepo from '../github/repo'; import { loginWithGitHub } from '../github/login-github'; -import { createDatabase } from './database'; +import { createDatabase, getDatabase } from './database'; import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; import { createSwa, updateSwaWithDatabaseConnectionStrings } from './swa'; @@ -195,7 +195,6 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. break; case 'GET': - if (projectType === 'projects') { // GET /accounts/${accountId}/projects/${projectId} if (projectId) { @@ -207,8 +206,16 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. return await listStorage({ ws, requestId, + projectName, projectNameUnique, - projectName + }); + // GET /accounts/${accountId}/projects/${projectId}/database + } else if (providerType === 'database') { + return await getDatabase({ + ws, + requestId, + projectName, + projectNameUnique }); } } From 86063c6880b2f5ac896a46df0ec1d948bf2b7fb9 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 19:39:19 +0200 Subject: [PATCH 04/29] chore: rename x-tag to thunderstorm --- src/features/thunderstorm/database.ts | 8 ++++---- src/features/thunderstorm/index.ts | 3 ++- src/features/thunderstorm/project.ts | 2 +- src/features/thunderstorm/storage.ts | 2 +- src/features/thunderstorm/swa.ts | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/features/thunderstorm/database.ts b/src/features/thunderstorm/database.ts index d621f5e..4edf2a6 100644 --- a/src/features/thunderstorm/database.ts +++ b/src/features/thunderstorm/database.ts @@ -21,7 +21,7 @@ export async function createDatabase({ ws, requestId, projectName, projectNameUn --kind "MongoDB" \ --server-version "4.0" \ --default-consistency-level "Eventual" \ - --tag "x-created-by=hexa" \ + --tag "x-created-by=thunderstorm" \ --enable-multiple-write-locations false \ --enable-automatic-failover false \ --query "{id: id, name: name, tags: tags, endpoint: documentEndpoint}"` @@ -71,7 +71,7 @@ export async function getDatabase({ ws, requestId, projectName, projectNameUniqu sendWebSocketResponse(ws, requestId, { resource: 'DATABASE', }, 202); - + const cosmosdbConnectionString = await getDatabaseConnectionString({ projectName, projectNameUnique @@ -79,9 +79,9 @@ export async function getDatabase({ ws, requestId, projectName, projectNameUniqu const client = await MongoClient.connect(cosmosdbConnectionString.connectionStrings[0].connectionString); const database = client.db(projectNameUnique); - + const collections = await database.listCollections().toArray() as any; - + for (const collection of collections) { collection.documents = await database.collection(collection.name).find().toArray(); } diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 0c99417..984600a 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -181,7 +181,8 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. // end operation sendWebSocketResponse(ws, requestId, { - projectName: projectNameUnique + projectName: projectNameUnique, + swa }, 201); } catch (error) { diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index ab22721..0a24858 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -13,7 +13,7 @@ export async function createProject({ ws, requestId, projectName, projectNameUni `group create \ --location "${location}" \ --name "${projectName}" \ - --tag "x-created-by=hexa" \ + --tag "x-created-by=thunderstorm" \ --tag "x-project-name="${projectNameUnique}" \ --query "{name:name, id:id, location:location}"` ); diff --git a/src/features/thunderstorm/storage.ts b/src/features/thunderstorm/storage.ts index 8b50ece..b9a8691 100644 --- a/src/features/thunderstorm/storage.ts +++ b/src/features/thunderstorm/storage.ts @@ -32,7 +32,7 @@ export async function createStorage({ ws, location, requestId, projectName, proj --subscription "${subscriptionId}" \ --resource-group "${projectName}" \ --kind "StorageV2" \ - --tag "x-created-by=hexa" \ + --tag "x-created-by=thunderstorm" \ --query "{name:name, id:id, location:location}"` ); diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index 4d9985d..6609a9e 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -21,7 +21,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, --api-location "api" \ --app-location "./" \ --token "${gitHubToken}" \ - --tag "x-created-by=hexa" \ + --tag "x-created-by=thunderstorm" \ --sku "free" \ --debug \ --query "{name:name, id:id, url:defaultHostname}"`, From 5bdb141ab9abd3b26bb4c0f9a257993d4726bc00 Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Thu, 5 Aug 2021 19:41:28 +0200 Subject: [PATCH 05/29] feat: add possibility to retrieve github functions --- src/features/thunderstorm/index.ts | 6 +++++- src/features/thunderstorm/storage.ts | 1 - src/features/thunderstorm/swa.ts | 31 ++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 0c99417..84567da 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -7,7 +7,7 @@ import { loginWithGitHub } from '../github/login-github'; import { createDatabase, getDatabase } from './database'; import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; -import { createSwa, updateSwaWithDatabaseConnectionStrings } from './swa'; +import { createSwa, listFunctions, updateSwaWithDatabaseConnectionStrings } from './swa'; type WsRequest = { method: 'LOGINAZURE' | 'LOGINGITHUB' | 'GET' | 'POST' | 'DELETE' | 'PUT' | 'STATUS'; @@ -217,6 +217,10 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. projectName, projectNameUnique }); + } else if (providerType === 'functions') { + return await listFunctions({ + ws, requestId, projectName, projectNameUnique + }); } } // GET /accounts/${accountId}/projects/ diff --git a/src/features/thunderstorm/storage.ts b/src/features/thunderstorm/storage.ts index 8b50ece..87cdb95 100644 --- a/src/features/thunderstorm/storage.ts +++ b/src/features/thunderstorm/storage.ts @@ -59,7 +59,6 @@ export async function createStorage({ ws, location, requestId, projectName, proj resource: 'STORAGE', error }, 500); - } } diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index 4d9985d..3f8415b 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -53,3 +53,34 @@ export async function updateSwaWithDatabaseConnectionStrings({ projectNameUnique // --setting-names 'COSMOSDB_CONNECTION_STRING="${databaseConnectionString}"'` // ); } + + +export async function listFunctions({ ws, requestId, projectName, projectNameUnique }: any) { + try { + + sendWebSocketResponse(ws, requestId, { + resource: 'FUNCTIONS', + }, 202); + + let functions = await az>( + `staticwebapp environment functions \ + --name "${projectNameUnique}" + --resource-group "${projectName}" \ + `); + + return sendWebSocketResponse(ws, requestId, { + resource: 'FUNCTIONS', + functions + }, 200); + + + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'STORAGE', + error + }, 500); + + } +} \ No newline at end of file From 24dc91b5b236e37a6fbb7a3c1c1e6c1aed177d47 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 19:39:19 +0200 Subject: [PATCH 06/29] chore: rename x-tag to thunderstorm --- src/features/thunderstorm/project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index 0a24858..cce6ca8 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -43,7 +43,7 @@ export async function listProjects({ ws, requestId, accountId }: any) { let resourceGroupsList = await az( `group list --subscription "${accountId}" --query "[].{name:name, id:id, location:location, tags:tags}"` ); - resourceGroupsList = resourceGroupsList.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "hexa")); + resourceGroupsList = resourceGroupsList.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "thunderstorm")); sendWebSocketResponse(ws, requestId, { projects: resourceGroupsList }, 200); From b1bb82f3c3ab2e55960cbf0836de2d8de93f01a0 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 19:55:12 +0200 Subject: [PATCH 07/29] fix: rename subscriptionId with accountId --- src/features/thunderstorm/storage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/thunderstorm/storage.ts b/src/features/thunderstorm/storage.ts index 500c65b..ec276f5 100644 --- a/src/features/thunderstorm/storage.ts +++ b/src/features/thunderstorm/storage.ts @@ -18,7 +18,7 @@ export async function getStorageConnectionString({ projectNameUnique }: any) { ); } -export async function createStorage({ ws, location, requestId, projectName, projectNameUnique, subscriptionId }: any) { +export async function createStorage({ ws, location, requestId, projectName, projectNameUnique, accountId }: any) { try { sendWebSocketResponse(ws, requestId, { @@ -29,7 +29,7 @@ export async function createStorage({ ws, location, requestId, projectName, proj `storage account create \ --location "${location}" \ --name "${projectNameUnique}" \ - --subscription "${subscriptionId}" \ + --subscription "${accountId}" \ --resource-group "${projectName}" \ --kind "StorageV2" \ --tag "x-created-by=thunderstorm" \ From fa22abe9dcd616e1c7937b91782fd126a9928c30 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 20:26:37 +0200 Subject: [PATCH 08/29] fix: remove swa response --- src/features/thunderstorm/index.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index cb4fdf5..ca61430 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -1,9 +1,9 @@ -import WebSocket from 'ws'; import chalk from 'chalk'; import util from 'util'; +import WebSocket from 'ws'; import { az } from '../../core/utils'; -import createGitHubRepo from '../github/repo'; import { loginWithGitHub } from '../github/login-github'; +import createGitHubRepo from '../github/repo'; import { createDatabase, getDatabase } from './database'; import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; @@ -160,7 +160,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. location }); - const databse = createDatabase({ + const database = createDatabase({ ws, requestId, projectName, @@ -168,7 +168,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. accountId }); - await Promise.all([swa, storage, databse]); + await Promise.all([swa, storage, database]); console.log(`Database connection string: ${KeyVault.ConnectionString.Database}`); if (KeyVault.ConnectionString.Database) { @@ -181,8 +181,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. // end operation sendWebSocketResponse(ws, requestId, { - projectName: projectNameUnique, - swa + projectName: projectNameUnique }, 201); } catch (error) { @@ -210,7 +209,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. projectName, projectNameUnique, }); - // GET /accounts/${accountId}/projects/${projectId}/database + // GET /accounts/${accountId}/projects/${projectId}/database } else if (providerType === 'database') { return await getDatabase({ ws, From 5773eb65d7227f5a67221d5e1935a1ffdc401b8b Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Thu, 5 Aug 2021 20:58:52 +0200 Subject: [PATCH 09/29] feat: add method to retrieve swa infos --- src/features/thunderstorm/index.ts | 6 +++++- src/features/thunderstorm/swa.ts | 30 ++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index cb4fdf5..a41a46d 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -7,7 +7,7 @@ import { loginWithGitHub } from '../github/login-github'; import { createDatabase, getDatabase } from './database'; import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; -import { createSwa, listFunctions, updateSwaWithDatabaseConnectionStrings } from './swa'; +import { createSwa, getSWA, listFunctions, updateSwaWithDatabaseConnectionStrings } from './swa'; type WsRequest = { method: 'LOGINAZURE' | 'LOGINGITHUB' | 'GET' | 'POST' | 'DELETE' | 'PUT' | 'STATUS'; @@ -222,6 +222,10 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. return await listFunctions({ ws, requestId, projectName, projectNameUnique }); + } else if (providerType === 'swa') { + return await getSWA({ + ws, requestId, projectName, projectNameUnique + }); } } // GET /accounts/${accountId}/projects/ diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index 73fc659..ddc0601 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -54,10 +54,37 @@ export async function updateSwaWithDatabaseConnectionStrings({ projectNameUnique // ); } +export async function getSWA({ ws, requestId, projectName, projectNameUnique }: any) { + try { + sendWebSocketResponse(ws, requestId, { + resource: 'FUNCTIONS', + }, 202); + + let swa = await az>( + `staticwebapp show \ + --name "${projectNameUnique}" + --resource-group "${projectName}" \ + `); + + return sendWebSocketResponse(ws, requestId, { + resource: 'FUNCTIONS', + swa + }, 200); + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'FUNCTIONS', + error + }, 500); + + } +} + export async function listFunctions({ ws, requestId, projectName, projectNameUnique }: any) { try { - + sendWebSocketResponse(ws, requestId, { resource: 'FUNCTIONS', }, 202); @@ -81,6 +108,5 @@ export async function listFunctions({ ws, requestId, projectName, projectNameUni resource: 'STORAGE', error }, 500); - } } \ No newline at end of file From 317f00f4c8b122cb27f24eb79513858db3a61de1 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 21:38:43 +0200 Subject: [PATCH 10/29] fix: add resource name in tag --- package.json | 2 +- src/features/thunderstorm/index.ts | 38 +++++++++++++++++----------- src/features/thunderstorm/project.ts | 7 +++-- src/features/thunderstorm/swa.ts | 5 ++-- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 4ce7d3a..f2079ac 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "watch": "npm run build -- --watch", "prestart": "npm run build", - "start": "node ./dist/bin.js ws", + "start": "node ./dist/bin.js ws --verbose", "build": "rimraf dist/ && mkdir dist && npm run copy:templates && npm run copy:bin && tsc", "copy:templates": "copyfiles --verbose --all \"src/templates/**/*\" dist/", "copy:bin": "copyfiles --verbose --flat src/bin.js dist/", diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 5665ada..182361a 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -51,7 +51,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. // get project name from body (only sent when creating a project) let projectName = (body?.projectName)?.replace(/\s+/g, ''); - let projectNameUnique = undefined; + let projectNameUnique: undefined | string = undefined; // extract request metadata from URL // /accounts/${accountId}/projects/${projectId}/{storage,database}/${providerId} @@ -72,7 +72,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. if (projectName) { // some resources require that name values must be less than 24 characters with no whitespace - projectNameUnique = `${projectName + (Math.random() + 1).toString(36).substring(2)}`.substr(0, 24); + projectNameUnique = `${projectName + (Math.random() + 1).toString(36).substring(2)}`.substr(0, 24).replace(/\-/g, ''); } if (providerType && providerId) { @@ -168,21 +168,29 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. accountId }); - await Promise.all([swa, storage, database]); - console.log(`Database connection string: ${KeyVault.ConnectionString.Database}`); - if (KeyVault.ConnectionString.Database) { - await updateSwaWithDatabaseConnectionStrings({ - connectionStrings: KeyVault.ConnectionString.Database, - projectNameUnique - }); - console.log('updated SWA with connection string'); - } + await Promise.all([swa, storage, database]) + .then(async _ => { - // end operation - sendWebSocketResponse(ws, requestId, { - projectName: projectNameUnique - }, 201); + console.log(`Database connection string: ${KeyVault.ConnectionString.Database}`); + + if (KeyVault.ConnectionString.Database) { + await updateSwaWithDatabaseConnectionStrings({ + databaseConnectionString: KeyVault.ConnectionString.Database, + projectNameUnique + }); + console.log('updated SWA with connection string'); + } + + // end operation + sendWebSocketResponse(ws, requestId, { + projectName: projectNameUnique + }, 201); + }).catch(error => { + sendWebSocketResponse(ws, requestId, { + error + }, 500); + }); } catch (error) { console.error(chalk.red(error)); diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index cce6ca8..b6e540e 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -9,17 +9,20 @@ export async function createProject({ ws, requestId, projectName, projectNameUni resource: 'PROJECT' }, 202); - await az( + const project = await az( `group create \ --location "${location}" \ --name "${projectName}" \ --tag "x-created-by=thunderstorm" \ - --tag "x-project-name="${projectNameUnique}" \ + --tag "x-project-name=${projectName}" \ + --tag "x-resource-name=${projectNameUnique}" \ + --debug \ --query "{name:name, id:id, location:location}"` ); sendWebSocketResponse(ws, requestId, { resource: 'PROJECT', + project }, 200); } catch (error) { diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index ddc0601..ae6d644 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -23,7 +23,6 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, --token "${gitHubToken}" \ --tag "x-created-by=thunderstorm" \ --sku "free" \ - --debug \ --query "{name:name, id:id, url:defaultHostname}"`, ); @@ -42,7 +41,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, } } -export async function updateSwaWithDatabaseConnectionStrings({ projectNameUnique, databaseConnectionString }: any) { +export async function updateSwaWithDatabaseConnectionStrings({ projectNameUnique, databaseConnectionString }: { projectNameUnique: string | undefined, databaseConnectionString: string }) { console.log(`TODO: az staticwebapp appsettings set doesn not support updating app setting right now!`); console.log({ projectNameUnique, databaseConnectionString }); return Promise.resolve(); @@ -109,4 +108,4 @@ export async function listFunctions({ ws, requestId, projectName, projectNameUni error }, 500); } -} \ No newline at end of file +} From 1a1ebeeecdc20569bb02ea2e8dc2ca0606bce4e2 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Thu, 5 Aug 2021 22:56:52 +0200 Subject: [PATCH 11/29] fix: enable debug logs when creating swa --- src/features/thunderstorm/swa.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index ae6d644..ea01cec 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -23,6 +23,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, --token "${gitHubToken}" \ --tag "x-created-by=thunderstorm" \ --sku "free" \ + --debug \ --query "{name:name, id:id, url:defaultHostname}"`, ); From 4c043fa7b5599308abd6671051a6dda4652359c0 Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Fri, 6 Aug 2021 00:34:46 +0200 Subject: [PATCH 12/29] feat(collection): enable collection creation --- src/features/thunderstorm/database.ts | 37 +++++++ src/features/thunderstorm/index.ts | 143 ++++++++++++++------------ 2 files changed, 114 insertions(+), 66 deletions(-) diff --git a/src/features/thunderstorm/database.ts b/src/features/thunderstorm/database.ts index 4edf2a6..5e9187f 100644 --- a/src/features/thunderstorm/database.ts +++ b/src/features/thunderstorm/database.ts @@ -66,6 +66,43 @@ async function getDatabaseConnectionString({ projectNameUnique, projectName }: { ); } +export async function createCollection({ ws, requestId, projectName, projectNameUnique, collectionName }: any) { + try { + sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + }, 202); + + const cosmosdbConnectionString = await getDatabaseConnectionString({ + projectName, + projectNameUnique + }); + + const client = await MongoClient.connect(cosmosdbConnectionString.connectionStrings[0].connectionString); + const database = client.db(projectNameUnique); + + const collections = (await database.listCollections().toArray() as any).map((collection: any) => collection.name); + + if (!collections.includes(collectionName)) { + console.log(`collection ${collectionName} does not exist`); + await database.createCollection(collectionName); + } else { + console.log(`collection ${collectionName} already exists`); + } + + return sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE' + }, 200); + + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + error + }, 500); + } +} + export async function getDatabase({ ws, requestId, projectName, projectNameUnique }: any) { try { sendWebSocketResponse(ws, requestId, { diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 5665ada..9873ce4 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -4,7 +4,7 @@ import WebSocket from 'ws'; import { az } from '../../core/utils'; import { loginWithGitHub } from '../github/login-github'; import createGitHubRepo from '../github/repo'; -import { createDatabase, getDatabase } from './database'; +import { createCollection, createDatabase, getDatabase } from './database'; import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; import { createSwa, getSWA, listFunctions, updateSwaWithDatabaseConnectionStrings } from './swa'; @@ -101,89 +101,100 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. if (projectType === 'projects') { try { - sendWebSocketResponse(ws, requestId, { - resource: 'GITHUB' - }, 202); - - try { - - var { html_url, default_branch } = await createGitHubRepo({ - token: body.gitHubToken, + if (providerType === 'database') { + + return await createCollection({ + ws, + requestId, projectName, - }) - + projectNameUnique, + collectionName: body.collectionName + }); + + } else { sendWebSocketResponse(ws, requestId, { resource: 'GITHUB' - }, 200); + }, 202); - } catch (error) { - console.error(chalk.red(error)); + try { - return sendWebSocketResponse(ws, requestId, { - resource: 'GITHUB', - error - }, 500); + var { html_url, default_branch } = await createGitHubRepo({ + token: body.gitHubToken, + projectName, + }) - } + sendWebSocketResponse(ws, requestId, { + resource: 'GITHUB' + }, 200); - //====== + } catch (error) { + console.error(chalk.red(error)); - const resourceGroup = createProject({ - ws, - requestId, - projectName, - projectNameUnique, - location - }); + return sendWebSocketResponse(ws, requestId, { + resource: 'GITHUB', + error + }, 500); - await Promise.all([resourceGroup]); + } - //====== + //====== - const swa = createSwa({ - ws, - requestId, - projectName, - projectNameUnique, - location, - html_url, - default_branch, - gitHubToken: body.gitHubToken - }); + const resourceGroup = createProject({ + ws, + requestId, + projectName, + projectNameUnique, + location + }); - const storage = createStorage({ - ws, - requestId, - projectName, - projectNameUnique, - accountId, - location - }); + await Promise.all([resourceGroup]); - const database = createDatabase({ - ws, - requestId, - projectName, - projectNameUnique, - accountId - }); + //====== - await Promise.all([swa, storage, database]); - console.log(`Database connection string: ${KeyVault.ConnectionString.Database}`); + const swa = createSwa({ + ws, + requestId, + projectName, + projectNameUnique, + location, + html_url, + default_branch, + gitHubToken: body.gitHubToken + }); - if (KeyVault.ConnectionString.Database) { - await updateSwaWithDatabaseConnectionStrings({ - connectionStrings: KeyVault.ConnectionString.Database, - projectNameUnique + const storage = createStorage({ + ws, + requestId, + projectName, + projectNameUnique, + accountId, + location }); - console.log('updated SWA with connection string'); - } - // end operation - sendWebSocketResponse(ws, requestId, { - projectName: projectNameUnique - }, 201); + const database = createDatabase({ + ws, + requestId, + projectName, + projectNameUnique, + accountId + }); + + await Promise.all([swa, storage, database]); + console.log(`Database connection string: ${KeyVault.ConnectionString.Database}`); + if (KeyVault.ConnectionString.Database) { + await updateSwaWithDatabaseConnectionStrings({ + connectionStrings: KeyVault.ConnectionString.Database, + projectNameUnique + }); + console.log('updated SWA with connection string'); + } + + // end operation + sendWebSocketResponse(ws, requestId, { + projectName: projectNameUnique + }, 201); + } } catch (error) { console.error(chalk.red(error)); From 1879782c5e272312018f4ebfaa0c824db1728549 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Fri, 6 Aug 2021 01:07:52 +0200 Subject: [PATCH 13/29] fix: use proper tags construction --- src/features/thunderstorm/database.ts | 2 +- src/features/thunderstorm/project.ts | 4 +--- src/features/thunderstorm/storage.ts | 2 +- src/features/thunderstorm/swa.ts | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/features/thunderstorm/database.ts b/src/features/thunderstorm/database.ts index 4edf2a6..887f122 100644 --- a/src/features/thunderstorm/database.ts +++ b/src/features/thunderstorm/database.ts @@ -21,7 +21,7 @@ export async function createDatabase({ ws, requestId, projectName, projectNameUn --kind "MongoDB" \ --server-version "4.0" \ --default-consistency-level "Eventual" \ - --tag "x-created-by=thunderstorm" \ + --tags "x-created-by=thunderstorm" \ --enable-multiple-write-locations false \ --enable-automatic-failover false \ --query "{id: id, name: name, tags: tags, endpoint: documentEndpoint}"` diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index b6e540e..7332dfa 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -13,9 +13,7 @@ export async function createProject({ ws, requestId, projectName, projectNameUni `group create \ --location "${location}" \ --name "${projectName}" \ - --tag "x-created-by=thunderstorm" \ - --tag "x-project-name=${projectName}" \ - --tag "x-resource-name=${projectNameUnique}" \ + --tags "x-created-by=thunderstorm" "x-project-name=${projectName}" "x-resource-name=${projectNameUnique}" \ --debug \ --query "{name:name, id:id, location:location}"` ); diff --git a/src/features/thunderstorm/storage.ts b/src/features/thunderstorm/storage.ts index ec276f5..9ea2683 100644 --- a/src/features/thunderstorm/storage.ts +++ b/src/features/thunderstorm/storage.ts @@ -32,7 +32,7 @@ export async function createStorage({ ws, location, requestId, projectName, proj --subscription "${accountId}" \ --resource-group "${projectName}" \ --kind "StorageV2" \ - --tag "x-created-by=thunderstorm" \ + --tags "x-created-by=thunderstorm" \ --query "{name:name, id:id, location:location}"` ); diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index ea01cec..8fa552a 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -21,7 +21,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, --api-location "api" \ --app-location "./" \ --token "${gitHubToken}" \ - --tag "x-created-by=thunderstorm" \ + --tags "x-created-by=thunderstorm" \ --sku "free" \ --debug \ --query "{name:name, id:id, url:defaultHostname}"`, @@ -29,7 +29,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, sendWebSocketResponse(ws, requestId, { resource: 'SWA', - url: swa.url + url: swa.url, }, 200); } catch (error) { From 474d89800e5cff3a9afdd75a82a152cb300782e0 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Fri, 6 Aug 2021 02:28:49 +0200 Subject: [PATCH 14/29] feat: add environment variables support --- src/features/thunderstorm/database.ts | 2 +- src/features/thunderstorm/env.ts | 39 +++++++++++++++++++++++++++ src/features/thunderstorm/index.ts | 28 +++++++++++++------ 3 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 src/features/thunderstorm/env.ts diff --git a/src/features/thunderstorm/database.ts b/src/features/thunderstorm/database.ts index 887f122..de0b1d0 100644 --- a/src/features/thunderstorm/database.ts +++ b/src/features/thunderstorm/database.ts @@ -57,7 +57,7 @@ export async function createDatabase({ ws, requestId, projectName, projectNameUn } -async function getDatabaseConnectionString({ projectNameUnique, projectName }: { projectNameUnique: string, projectName: string }) { +export async function getDatabaseConnectionString({ projectNameUnique, projectName }: { projectNameUnique: string, projectName: string }) { return await az<{ connectionStrings: Array<{ connectionString: string, description: string }> }>( `cosmosdb keys list \ --name "${projectNameUnique}" \ diff --git a/src/features/thunderstorm/env.ts b/src/features/thunderstorm/env.ts new file mode 100644 index 0000000..30b8ef9 --- /dev/null +++ b/src/features/thunderstorm/env.ts @@ -0,0 +1,39 @@ +import chalk from "chalk"; +import { sendWebSocketResponse } from "."; +import { getDatabaseConnectionString } from "./database"; +import { getStorageConnectionString } from "./storage"; + +export async function listEnvironmentVariables({ ws, requestId, projectNameUnique, projectName }: any) { + + try { + + sendWebSocketResponse(ws, requestId, { + resource: 'ENV', + }, 202); + + const databaseConnectionStrings = await getDatabaseConnectionString({ + projectNameUnique, + projectName, + }); + + const storageConnectionString = await getStorageConnectionString({ + projectNameUnique + }); + + sendWebSocketResponse(ws, requestId, { + env: { + database: databaseConnectionStrings.connectionStrings.pop()?.connectionString, + storage: storageConnectionString.connectionString + } + }, 200); + + } catch (error) { + console.error(chalk.red(error)); + + return sendWebSocketResponse(ws, requestId, { + resource: 'ENV', + error + }, 500); + } + +} diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 182361a..4005ac6 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -5,6 +5,7 @@ import { az } from '../../core/utils'; import { loginWithGitHub } from '../github/login-github'; import createGitHubRepo from '../github/repo'; import { createDatabase, getDatabase } from './database'; +import { listEnvironmentVariables } from './env'; import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; import { createSwa, getSWA, listFunctions, updateSwaWithDatabaseConnectionStrings } from './swa'; @@ -54,7 +55,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. let projectNameUnique: undefined | string = undefined; // extract request metadata from URL - // /accounts/${accountId}/projects/${projectId}/{storage,database}/${providerId} + // /accounts/{accountId}/projects/{projectId}/{storage,function,env,swa,database}/{providerId} const [ _, _accountsLabel, @@ -204,12 +205,13 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. case 'GET': if (projectType === 'projects') { - // GET /accounts/${accountId}/projects/${projectId} + + // GET /accounts/{accountId}/projects/{projectId} if (projectId) { if (!providerType) { // TODO: list all resource groups } - // GET /accounts/${accountId}/projects/${projectId}/storages + // GET /accounts/{accountId}/projects/{projectId}/storages/{providerId} else if (providerType === 'storages') { return await listStorage({ ws, @@ -217,25 +219,35 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. projectName, projectNameUnique, }); - // GET /accounts/${accountId}/projects/${projectId}/database - } else if (providerType === 'database') { + // GET /accounts/{accountId}/projects/{projectId}/databases/{providerId} + } else if (providerType === 'databases') { return await getDatabase({ ws, requestId, projectName, projectNameUnique }); - } else if (providerType === 'functions') { + } + // GET /accounts/{accountId}/projects/{projectId}/functions/{providerId} + else if (providerType === 'functions') { return await listFunctions({ ws, requestId, projectName, projectNameUnique }); - } else if (providerType === 'swa') { + } + // GET /accounts/{accountId}/projects/{projectId}/swa/{providerId} + else if (providerType === 'swa') { return await getSWA({ ws, requestId, projectName, projectNameUnique }); } + // GET /accounts/{accountId}/projects/{projectId}/env/{providerId} + else if (providerType === 'env') { + return await listEnvironmentVariables({ + ws, requestId, projectNameUnique, projectName + }); + } } - // GET /accounts/${accountId}/projects/ + // GET /accounts/{accountId}/projects/ else { await listProjects({ ws, From edcb71ff7dc9077c1ee7b0b14859d02a1f643e12 Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Fri, 6 Aug 2021 06:44:17 +0200 Subject: [PATCH 15/29] fix: fix --- src/features/thunderstorm/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index c762eae..c88b4e1 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -4,12 +4,8 @@ import WebSocket from 'ws'; import { az } from '../../core/utils'; import { loginWithGitHub } from '../github/login-github'; import createGitHubRepo from '../github/repo'; -<<<<<<< HEAD -import { createCollection, createDatabase, getDatabase } from './database'; -======= -import { createDatabase, getDatabase } from './database'; +import { createDatabase, createCollection, getDatabase } from './database'; import { listEnvironmentVariables } from './env'; ->>>>>>> 474d89800e5cff3a9afdd75a82a152cb300782e0 import { createProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; import { createSwa, getSWA, listFunctions, updateSwaWithDatabaseConnectionStrings } from './swa'; From 118d62909b3ba38321ea154a4df693c8d1fb65eb Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Fri, 6 Aug 2021 06:48:12 +0200 Subject: [PATCH 16/29] fix: use databases instead of database --- src/features/thunderstorm/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index c88b4e1..6b6a653 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -102,8 +102,8 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. if (projectType === 'projects') { try { - if (providerType === 'database') { - + if (providerType === 'databases') { + return await createCollection({ ws, requestId, @@ -111,7 +111,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. projectNameUnique, collectionName: body.collectionName }); - + } else { sendWebSocketResponse(ws, requestId, { resource: 'GITHUB' From 801726a9a75c80a57e53918e11921e176cad21f6 Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Sat, 7 Aug 2021 22:41:11 +0200 Subject: [PATCH 17/29] feat(project name): add x-project-name tag to resource group Also move old projectName to projectId tag --- src/features/thunderstorm/index.ts | 4 +++- src/features/thunderstorm/project.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 6b6a653..d7bf153 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -52,6 +52,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. // get project name from body (only sent when creating a project) let projectName = (body?.projectName)?.replace(/\s+/g, ''); + let projectRealName = body?.projectName; let projectNameUnique: undefined | string = undefined; // extract request metadata from URL @@ -145,7 +146,8 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. requestId, projectName, projectNameUnique, - location + location, + projectRealName }); await Promise.all([resourceGroup]); diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index 7332dfa..3cef08c 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -2,7 +2,7 @@ import chalk from "chalk"; import { sendWebSocketResponse } from "."; import { az } from "../../core/utils"; -export async function createProject({ ws, requestId, projectName, projectNameUnique, location }: any) { +export async function createProject({ ws, requestId, projectName, projectNameUnique, location, projectRealName }: any) { try { sendWebSocketResponse(ws, requestId, { @@ -13,7 +13,7 @@ export async function createProject({ ws, requestId, projectName, projectNameUni `group create \ --location "${location}" \ --name "${projectName}" \ - --tags "x-created-by=thunderstorm" "x-project-name=${projectName}" "x-resource-name=${projectNameUnique}" \ + --tags "x-created-by=thunderstorm" "x-project-name=${projectRealName}" "x-project-id=${projectName}" "x-resource-name=${projectNameUnique}" \ --debug \ --query "{name:name, id:id, location:location}"` ); From 5b638a4b2b3354389a5e303e1ceba10b40cc2537 Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Mon, 9 Aug 2021 11:05:41 +0200 Subject: [PATCH 18/29] feat(projectrealname): add project real name to swa --- src/features/thunderstorm/index.ts | 3 ++- src/features/thunderstorm/swa.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index d7bf153..cc9a95e 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -162,7 +162,8 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. location, html_url, default_branch, - gitHubToken: body.gitHubToken + gitHubToken: body.gitHubToken, + projectRealName }); const storage = createStorage({ diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index 8fa552a..ba2168b 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -3,7 +3,7 @@ import { sendWebSocketResponse } from "."; import { az } from "../../core/utils"; -export async function createSwa({ ws, requestId, projectName, projectNameUnique, location, html_url, default_branch, gitHubToken }: any) { +export async function createSwa({ ws, requestId, projectName, projectNameUnique, location, html_url, default_branch, gitHubToken, projectRealName }: any) { try { sendWebSocketResponse(ws, requestId, { @@ -21,7 +21,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, --api-location "api" \ --app-location "./" \ --token "${gitHubToken}" \ - --tags "x-created-by=thunderstorm" \ + --tags "x-created-by=thunderstorm" "x-project-name=${projectRealName}" \ --sku "free" \ --debug \ --query "{name:name, id:id, url:defaultHostname}"`, From 48698c55bae4a390627167c9d4d6dbc8b6db2bc1 Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Mon, 9 Aug 2021 11:56:26 +0200 Subject: [PATCH 19/29] fix(swa): return swa list instead of resource groups + add tags to swa --- src/features/thunderstorm/project.ts | 10 ++++++---- src/features/thunderstorm/swa.ts | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index 3cef08c..8c9b813 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -41,12 +41,14 @@ export async function listProjects({ ws, requestId, accountId }: any) { resource: 'PROJECT', }, 202); - let resourceGroupsList = await az( - `group list --subscription "${accountId}" --query "[].{name:name, id:id, location:location, tags:tags}"` + let staticWebApps = await az( + `staticwebapp list --subscription "${accountId}"` ); - resourceGroupsList = resourceGroupsList.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "thunderstorm")); + staticWebApps = staticWebApps.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "thunderstorm")); sendWebSocketResponse(ws, requestId, { - projects: resourceGroupsList + projects: staticWebApps.map((swa: any) => { + return { swa }; + }); }, 200); } catch (error) { console.error(chalk.red(error)); diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index ba2168b..b9324f8 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -21,7 +21,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, --api-location "api" \ --app-location "./" \ --token "${gitHubToken}" \ - --tags "x-created-by=thunderstorm" "x-project-name=${projectRealName}" \ + --tags "x-created-by=thunderstorm" "x-project-name=${projectRealName}" "x-project-id=${projectName}" "x-resource-name=${projectNameUnique}" \ --sku "free" \ --debug \ --query "{name:name, id:id, url:defaultHostname}"`, From 8b5217292f46f876f26d6e2176791282155ec7cf Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Mon, 9 Aug 2021 12:09:39 +0200 Subject: [PATCH 20/29] fix: remove extra semicolon --- src/features/thunderstorm/project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index 8c9b813..6654fa0 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -48,7 +48,7 @@ export async function listProjects({ ws, requestId, accountId }: any) { sendWebSocketResponse(ws, requestId, { projects: staticWebApps.map((swa: any) => { return { swa }; - }); + }) }, 200); } catch (error) { console.error(chalk.red(error)); From 122d7fd216878a5cd11f11fca09c0d5e4e16c003 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Mon, 9 Aug 2021 22:33:11 +0200 Subject: [PATCH 21/29] fix: project deletion --- src/features/thunderstorm/index.ts | 65 +++++++++++----------------- src/features/thunderstorm/project.ts | 42 +++++++++++++++++- src/type.d.ts | 2 +- 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index cc9a95e..346cb83 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -6,7 +6,7 @@ import { loginWithGitHub } from '../github/login-github'; import createGitHubRepo from '../github/repo'; import { createDatabase, createCollection, getDatabase } from './database'; import { listEnvironmentVariables } from './env'; -import { createProject, listProjects } from './project'; +import { createProject, deleteProject, listProjects } from './project'; import { createStorage, listStorage } from './storage'; import { createSwa, getSWA, listFunctions, updateSwaWithDatabaseConnectionStrings } from './swa'; @@ -43,7 +43,7 @@ export function sendWebSocketResponse(ws: WebSocket, requestId: string, body: Ob console.log(``); console.log(`Response:`); - console.log(util.inspect(response, { depth: 4, colors: true })); + console.log(util.inspect(response, { depth: 6, colors: true })); } export async function processWebSocketRequest(ws: WebSocket, message: WebSocket.Data) { @@ -183,28 +183,28 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. accountId }); - await Promise.all([swa, storage, database]) - .then(async _ => { - - console.log(`Database connection string: ${KeyVault.ConnectionString.Database}`); - - if (KeyVault.ConnectionString.Database) { - await updateSwaWithDatabaseConnectionStrings({ - databaseConnectionString: KeyVault.ConnectionString.Database, - projectNameUnique - }); - console.log('updated SWA with connection string'); - } - - // end operation - sendWebSocketResponse(ws, requestId, { - projectName: projectNameUnique - }, 201); - }).catch(error => { - sendWebSocketResponse(ws, requestId, { - error - }, 500); - }); + await Promise.all([swa, storage, database]) + .then(async _ => { + + console.log(`Database connection string: ${KeyVault.ConnectionString.Database}`); + + if (KeyVault.ConnectionString.Database) { + await updateSwaWithDatabaseConnectionStrings({ + databaseConnectionString: KeyVault.ConnectionString.Database, + projectNameUnique + }); + console.log('updated SWA with connection string'); + } + + // end operation + sendWebSocketResponse(ws, requestId, { + projectName: projectNameUnique + }, 201); + }).catch(error => { + sendWebSocketResponse(ws, requestId, { + error + }, 500); + }); if (KeyVault.ConnectionString.Database) { await updateSwaWithDatabaseConnectionStrings({ @@ -317,22 +317,7 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. case 'DELETE': if (projectType === 'projects') { - try { - - sendWebSocketResponse(ws, requestId, null, 202); - await az( - `group delete \ - --name "${projectName}" \ - --subscription "${accountId}" \ - --yes` - ); - sendWebSocketResponse(ws, requestId, null, 200); - } - catch (error) { - sendWebSocketResponse(ws, requestId, { - error - }, 500); - } + await deleteProject({ ws, requestId, projectName, projectRealName, accountId, projectNameUnique }); } break; diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index 6654fa0..5d86899 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -2,6 +2,10 @@ import chalk from "chalk"; import { sendWebSocketResponse } from "."; import { az } from "../../core/utils"; +function generateTags({ projectRealName, projectName, projectNameUnique }: any) { + return `"x-created-by=thunderstorm" "x-project-name=${projectRealName}" "x-project-id=${projectName}" "x-resource-name=${projectNameUnique}"`; +} + export async function createProject({ ws, requestId, projectName, projectNameUnique, location, projectRealName }: any) { try { @@ -13,7 +17,7 @@ export async function createProject({ ws, requestId, projectName, projectNameUni `group create \ --location "${location}" \ --name "${projectName}" \ - --tags "x-created-by=thunderstorm" "x-project-name=${projectRealName}" "x-project-id=${projectName}" "x-resource-name=${projectNameUnique}" \ + --tags ${generateTags({ projectRealName, projectName, projectNameUnique })} \ --debug \ --query "{name:name, id:id, location:location}"` ); @@ -44,7 +48,7 @@ export async function listProjects({ ws, requestId, accountId }: any) { let staticWebApps = await az( `staticwebapp list --subscription "${accountId}"` ); - staticWebApps = staticWebApps.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "thunderstorm")); + staticWebApps = staticWebApps.filter((a, _b) => (a.tags && a.tags["x-created-by"] === "thunderstorm" && a.tags["x-thundr-status"] !== "deleted")); sendWebSocketResponse(ws, requestId, { projects: staticWebApps.map((swa: any) => { return { swa }; @@ -60,3 +64,37 @@ export async function listProjects({ ws, requestId, accountId }: any) { } } + +export async function deleteProject({ ws, requestId, projectName, projectRealName, accountId, projectNameUnique }: any) { + try { + + sendWebSocketResponse(ws, requestId, null, 202); + + await az( + `staticwebapp update \ + --name "${projectNameUnique}" + --tags ${generateTags({ projectRealName, projectName, projectNameUnique })} "x-thundr-status=deleted"` + ); + await az( + `group update \ + --resource-group "${projectName}" \ + --subscription "${accountId}" + --set "tags.x-thundr-status=deleted"` + ); + await az( + `group delete \ + --name "${projectName}" \ + --subscription "${accountId}" \ + --no-wait \ + --yes` + ); + + sendWebSocketResponse(ws, requestId, null, 200); + + } + catch (error) { + sendWebSocketResponse(ws, requestId, { + error + }, 500); + } +} diff --git a/src/type.d.ts b/src/type.d.ts index d8e2475..8c1e023 100644 --- a/src/type.d.ts +++ b/src/type.d.ts @@ -37,7 +37,7 @@ declare interface HexaWorkspace { declare interface AzureEntity { id: string & CreationMode; name: string; - tags?: { "x-created-by": "hexa" } | { [key: string]: string }; + tags?: { [key: string]: string }; } declare interface AzureCosmosDBInstance {} From dae924ab20442d802f9ca7396381ea041eb51918 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Fri, 13 Aug 2021 00:55:11 +0200 Subject: [PATCH 22/29] feat: add --demo --- src/commands/ws.ts | 3 +- src/core/utils.ts | 4 ++ src/features/github/repo.ts | 29 ++++++---- src/features/thunderstorm/database.ts | 80 +++++++++++++++------------ src/features/thunderstorm/project.ts | 16 ++++-- src/features/thunderstorm/storage.ts | 26 +++++---- src/features/thunderstorm/swa.ts | 16 ++++-- src/index.ts | 10 +++- 8 files changed, 118 insertions(+), 66 deletions(-) diff --git a/src/commands/ws.ts b/src/commands/ws.ts index e8c8829..da1dcc5 100644 --- a/src/commands/ws.ts +++ b/src/commands/ws.ts @@ -1,3 +1,4 @@ +import chalk from 'chalk'; import WebSocket from 'ws'; import { processWebSocketRequest, sendWebSocketResponse } from '../features/thunderstorm/index'; @@ -5,7 +6,7 @@ export default async function () { const wss = new WebSocket.Server({ host: '0.0.0.0', port: 8080 }) wss.on('listening', () => { - console.log('Listening on ws://0.0.0.0:8080') + console.info(chalk.green("✔ Listening on ws://0.0.0.0:8080")); }); wss.on('connection', async ws => { diff --git a/src/core/utils.ts b/src/core/utils.ts index fd47274..80bda15 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -489,3 +489,7 @@ export async function* traverseFolder(folder: string): AsyncGenerator { } } } + +export function IS_DEMO() { + return process.env.DEMO_MODE === "hexa"; +} diff --git a/src/features/github/repo.ts b/src/features/github/repo.ts index 6f94c10..0648c23 100644 --- a/src/features/github/repo.ts +++ b/src/features/github/repo.ts @@ -1,15 +1,22 @@ import { Octokit } from "@octokit/rest"; +import { IS_DEMO } from "../../core/utils"; export default async function ({ token, projectName }: { token: string, projectName: string }) { - const octokit = new Octokit({ - auth: token, - }); - const { data } = await octokit.repos.createUsingTemplate({ - name: projectName, - template_repo: "vanilla-api", - template_owner: "staticwebdev", - include_all_branches: false, - private: true - }); - return data; + + if (IS_DEMO()) { + return await new Promise(resolve => setTimeout(resolve, 5000, {})); + } + else { + const octokit = new Octokit({ + auth: token, + }); + const { data } = await octokit.repos.createUsingTemplate({ + name: projectName, + template_repo: "vanilla-api", + template_owner: "staticwebdev", + include_all_branches: false, + private: true + }); + return data; + } }; diff --git a/src/features/thunderstorm/database.ts b/src/features/thunderstorm/database.ts index 9e17790..72f7c4e 100644 --- a/src/features/thunderstorm/database.ts +++ b/src/features/thunderstorm/database.ts @@ -2,7 +2,7 @@ import chalk from "chalk"; import { MongoClient } from "mongodb"; import { sendWebSocketResponse } from "."; -import { az } from "../../core/utils"; +import { az, IS_DEMO } from "../../core/utils"; export async function createDatabase({ ws, requestId, projectName, projectNameUnique }: any) { try { @@ -11,11 +11,17 @@ export async function createDatabase({ ws, requestId, projectName, projectNameUn resource: 'DATABASE', }, 202); - // TODO: enable free tier for non-Internal subscriptions - // --enable-free-tier \ + if (IS_DEMO()) { + await new Promise(resolve => setTimeout(resolve, 5000, {})); + } + else { + + + // TODO: enable free tier for non-Internal subscriptions + // --enable-free-tier \ - await az( - `cosmosdb create \ + await az( + `cosmosdb create \ --name "${projectNameUnique}" \ --resource-group "${projectName}" \ --kind "MongoDB" \ @@ -25,25 +31,27 @@ export async function createDatabase({ ws, requestId, projectName, projectNameUn --enable-multiple-write-locations false \ --enable-automatic-failover false \ --query "{id: id, name: name, tags: tags, endpoint: documentEndpoint}"` - ); + ); - await az( - `cosmosdb mongodb database create \ + await az( + `cosmosdb mongodb database create \ --name "${projectNameUnique}" \ --account-name "${projectNameUnique}" \ --resource-group "${projectName}"` - ); + ); - // fetch connection strings - const connectionStrings = await getDatabaseConnectionString({ - projectNameUnique, - projectName, - }); + // fetch connection strings + const connectionStrings = await getDatabaseConnectionString({ + projectNameUnique, + projectName, + }); - sendWebSocketResponse(ws, requestId, { - resource: 'DATABASE', - connectionString: connectionStrings.connectionStrings[0].connectionString, - }, 200); + sendWebSocketResponse(ws, requestId, { + resource: 'DATABASE', + connectionString: connectionStrings.connectionStrings[0].connectionString, + }, 200); + + } } catch (error) { console.error(chalk.red(error)); @@ -72,23 +80,27 @@ export async function createCollection({ ws, requestId, projectName, projectName resource: 'DATABASE', }, 202); - const cosmosdbConnectionString = await getDatabaseConnectionString({ - projectName, - projectNameUnique - }); - - const client = await MongoClient.connect(cosmosdbConnectionString.connectionStrings[0].connectionString); - const database = client.db(projectNameUnique); - - const collections = (await database.listCollections().toArray() as any).map((collection: any) => collection.name); - - if (!collections.includes(collectionName)) { - console.log(`collection ${collectionName} does not exist`); - await database.createCollection(collectionName); - } else { - console.log(`collection ${collectionName} already exists`); + if (IS_DEMO()) { + await new Promise(resolve => setTimeout(resolve, 5000, {})); + } + else { + const cosmosdbConnectionString = await getDatabaseConnectionString({ + projectName, + projectNameUnique + }); + + const client = await MongoClient.connect(cosmosdbConnectionString.connectionStrings[0].connectionString); + const database = client.db(projectNameUnique); + + const collections = (await database.listCollections().toArray() as any).map((collection: any) => collection.name); + + if (!collections.includes(collectionName)) { + console.log(`collection ${collectionName} does not exist`); + await database.createCollection(collectionName); + } else { + console.log(`collection ${collectionName} already exists`); + } } - return sendWebSocketResponse(ws, requestId, { resource: 'DATABASE' }, 200); diff --git a/src/features/thunderstorm/project.ts b/src/features/thunderstorm/project.ts index 5d86899..b770be3 100644 --- a/src/features/thunderstorm/project.ts +++ b/src/features/thunderstorm/project.ts @@ -1,6 +1,8 @@ import chalk from "chalk"; import { sendWebSocketResponse } from "."; -import { az } from "../../core/utils"; +import { az, IS_DEMO } from "../../core/utils"; + + function generateTags({ projectRealName, projectName, projectNameUnique }: any) { return `"x-created-by=thunderstorm" "x-project-name=${projectRealName}" "x-project-id=${projectName}" "x-resource-name=${projectNameUnique}"`; @@ -12,16 +14,20 @@ export async function createProject({ ws, requestId, projectName, projectNameUni sendWebSocketResponse(ws, requestId, { resource: 'PROJECT' }, 202); + let project = {}; - const project = await az( - `group create \ + if (IS_DEMO()) { + project = await new Promise(resolve => setTimeout(resolve, 5000, {})); + } else { + project = await az( + `group create \ --location "${location}" \ --name "${projectName}" \ --tags ${generateTags({ projectRealName, projectName, projectNameUnique })} \ --debug \ --query "{name:name, id:id, location:location}"` - ); - + ); + } sendWebSocketResponse(ws, requestId, { resource: 'PROJECT', project diff --git a/src/features/thunderstorm/storage.ts b/src/features/thunderstorm/storage.ts index 9ea2683..f685166 100644 --- a/src/features/thunderstorm/storage.ts +++ b/src/features/thunderstorm/storage.ts @@ -1,6 +1,6 @@ import chalk from "chalk"; import { sendWebSocketResponse } from "."; -import { az } from "../../core/utils"; +import { az, IS_DEMO } from "../../core/utils"; type AzureBlobStorageItem = { name: string, @@ -25,8 +25,13 @@ export async function createStorage({ ws, location, requestId, projectName, proj resource: 'STORAGE', }, 202); - await az( - `storage account create \ + if (IS_DEMO()) { + // await new Promise(resolve => setTimeout(resolve, 5000, {})); + throw new Error('Demo Error'); + } + else { + await az( + `storage account create \ --location "${location}" \ --name "${projectNameUnique}" \ --subscription "${accountId}" \ @@ -34,19 +39,20 @@ export async function createStorage({ ws, location, requestId, projectName, proj --kind "StorageV2" \ --tags "x-created-by=thunderstorm" \ --query "{name:name, id:id, location:location}"` - ); + ); - const storageConnectionString = await getStorageConnectionString({ - projectNameUnique - }); + const storageConnectionString = await getStorageConnectionString({ + projectNameUnique + }); - await az( - `storage container create \ + await az( + `storage container create \ --resource-group "${projectName}" \ --name "${projectNameUnique}" \ --public-access "blob" \ --connection-string "${storageConnectionString.connectionString}"` - ); + ); + } sendWebSocketResponse(ws, requestId, { resource: 'STORAGE', diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index b9324f8..709bee6 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -1,6 +1,6 @@ import chalk from "chalk"; import { sendWebSocketResponse } from "."; -import { az } from "../../core/utils"; +import { az, IS_DEMO } from "../../core/utils"; export async function createSwa({ ws, requestId, projectName, projectNameUnique, location, html_url, default_branch, gitHubToken, projectRealName }: any) { @@ -10,8 +10,15 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, resource: 'SWA' }, 202); - const swa = await az( - `staticwebapp create \ + let swa = { url: '' }; + + if (IS_DEMO()) { + swa = await new Promise(resolve => setTimeout(resolve, 5000, { url: '' })); + } + else { + + swa = await az( + `staticwebapp create \ --name "${projectNameUnique}" \ --resource-group "${projectName}" \ --source "${html_url}" \ @@ -25,7 +32,8 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, --sku "free" \ --debug \ --query "{name:name, id:id, url:defaultHostname}"`, - ); + ); + } sendWebSocketResponse(ws, requestId, { resource: 'SWA', diff --git a/src/index.ts b/src/index.ts index 6cb4075..ec6a77a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -54,11 +54,16 @@ export async function run() { .option("-y, --yes", "answer yes to all confirmations", false) .option("--dry-run", "do not execute real commands.", false) .option("--yolo", "enable all modes and all services", false) + .option("--demo", "enable demo mode", false) .parse(process.argv); // set confiuration // WARNING: order matters + if (program.demo) { + process.env.DEMO_MODE = "hexa"; + console.info(chalk.yellow("!!! Running is DEMO mode !!!")); + } if (program.debug) { process.env.DEBUG = "hexa"; } @@ -113,5 +118,8 @@ export async function run() { await runCommand(commandName.replace("--", ""), program.just); const end = process.hrtime(start); - console.info(chalk.green("✔ Done in %d seconds."), end[0]); + + if (!program.ws) { + console.info(chalk.green("✔ Done in %d seconds."), end[0]); + } } From ba5c59863daea32bf0f8d317f3f1d46765217e89 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Fri, 13 Aug 2021 01:22:23 +0200 Subject: [PATCH 23/29] fix: add proper typings for demo mode --- src/features/github/repo.ts | 2 +- src/features/thunderstorm/swa.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/github/repo.ts b/src/features/github/repo.ts index 0648c23..7d88525 100644 --- a/src/features/github/repo.ts +++ b/src/features/github/repo.ts @@ -4,7 +4,7 @@ import { IS_DEMO } from "../../core/utils"; export default async function ({ token, projectName }: { token: string, projectName: string }) { if (IS_DEMO()) { - return await new Promise(resolve => setTimeout(resolve, 5000, {})); + return await new Promise((resolve: Function) => setTimeout(resolve, 5000, { html_url: '', default_branch: '' })) as { html_url: string, default_branch: string }; } else { const octokit = new Octokit({ diff --git a/src/features/thunderstorm/swa.ts b/src/features/thunderstorm/swa.ts index 709bee6..d17cbcd 100644 --- a/src/features/thunderstorm/swa.ts +++ b/src/features/thunderstorm/swa.ts @@ -10,7 +10,7 @@ export async function createSwa({ ws, requestId, projectName, projectNameUnique, resource: 'SWA' }, 202); - let swa = { url: '' }; + let swa: AzureStaticWebApps = { id: 'MANUAL', url: '' }; if (IS_DEMO()) { swa = await new Promise(resolve => setTimeout(resolve, 5000, { url: '' })); From 7830cc32319b4162eae841f7f71c23fd3caf518c Mon Sep 17 00:00:00 2001 From: Olivier Leplus Date: Tue, 17 Aug 2021 10:28:26 +0200 Subject: [PATCH 24/29] feat(subscription): get subscriptions --- src/features/thunderstorm/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/features/thunderstorm/index.ts b/src/features/thunderstorm/index.ts index 346cb83..34d9ca3 100644 --- a/src/features/thunderstorm/index.ts +++ b/src/features/thunderstorm/index.ts @@ -281,9 +281,13 @@ export async function processWebSocketRequest(ws: WebSocket, message: WebSocket. accountId }); } - } - else { - sendWebSocketResponse(ws, requestId, { error: `resorce type not implemented "${projectType}"` }, 501); + } else if (requestId === 'SUBSCRIPTIONS') { + sendWebSocketResponse(ws, requestId, null, 202); + const subscriptions = await az(`account list`); + sendWebSocketResponse(ws, requestId, subscriptions); + + } else { + sendWebSocketResponse(ws, requestId, { error: `resource type not implemented "${projectType}"` }, 501); } break; From 2d6c742e2300aabec012bf35caf11b46131f5833 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Wed, 25 Aug 2021 10:08:11 +0200 Subject: [PATCH 25/29] chore: update website content --- api/hexa/function.json | 16 ---------------- api/hexa/index.js | 6 ------ api/host.json | 15 --------------- api/package-lock.json | 3 --- api/package.json | 9 --------- docs/index.html | 35 +++++++++-------------------------- docs/staticwebapp.config.json | 3 --- 7 files changed, 9 insertions(+), 78 deletions(-) delete mode 100644 api/hexa/function.json delete mode 100644 api/hexa/index.js delete mode 100644 api/host.json delete mode 100644 api/package-lock.json delete mode 100644 api/package.json delete mode 100644 docs/staticwebapp.config.json diff --git a/api/hexa/function.json b/api/hexa/function.json deleted file mode 100644 index 9c99c40..0000000 --- a/api/hexa/function.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": ["get", "post"] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} diff --git a/api/hexa/index.js b/api/hexa/index.js deleted file mode 100644 index 2ef1850..0000000 --- a/api/hexa/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = async function (context, req) { - context.res = { - status: req.query.statuscode || "200", - body: ["npm install --global @manekinekko/hexa", "hexa init", "hexa deploy", "hexa init --login", "hexa init --manual", "hexa init --yolo"], - }; -}; diff --git a/api/host.json b/api/host.json deleted file mode 100644 index 6ab6643..0000000 --- a/api/host.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } - }, - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[1.*, 2.0.0)" - } -} diff --git a/api/package-lock.json b/api/package-lock.json deleted file mode 100644 index 48e341a..0000000 --- a/api/package-lock.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "lockfileVersion": 1 -} diff --git a/api/package.json b/api/package.json deleted file mode 100644 index 15357df..0000000 --- a/api/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "", - "version": "", - "description": "", - "scripts": { - "test": "echo \"No tests yet...\"" - }, - "author": "" -} diff --git a/docs/index.html b/docs/index.html index f850217..3e8137d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -57,7 +57,6 @@ hexa title whitepreview