diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml new file mode 100644 index 0000000..437dd34 --- /dev/null +++ b/.github/workflows/deploy-functions.yml @@ -0,0 +1,73 @@ +name: Deploy Azure Functions + +on: + push: + branches: ["main"] + +concurrency: + group: functions-prod + cancel-in-progress: true + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + # Checkout repository code + - name: Checkout + uses: actions/checkout@v4 + + # Setup Node.js runtime + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + # Install dependencies for the whole monorepo (npm workspaces) + - name: Install dependencies + run: npm ci + + # Build ONLY the functions workspace (tsc output to functions/dist) + - name: Build functions workspace + run: npm run -w functions ci + + # Prepare a deploy folder that contains: + # - host.json at the root + # - dist/ at the root + # - a package.json that references the internal db package via "file:" + # - the internal package code under ./packages/db + - name: Prepare deploy folder + run: | + rm -rf deploy + mkdir -p deploy/packages + + # Copy runtime files expected by Azure Functions + cp functions/host.json deploy/host.json + cp -R functions/dist deploy/dist + + # Copy the internal workspace package so it can be installed with "file:" + cp -R packages/db deploy/packages/db + + # Generate a deploy-specific package.json + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('functions/package.json','utf8')); + pkg.name = 'functions-deploy'; + pkg.main = 'dist/index.js'; + pkg.dependencies = pkg.dependencies || {}; + pkg.dependencies['@embalse-info/db'] = 'file:./packages/db'; + fs.writeFileSync('deploy/package.json', JSON.stringify(pkg, null, 2)); + " + + # Generate a deploy-specific package-lock.json by installing in deploy/ + cd deploy + npm install --omit=dev + + # Deploy the deploy/ folder (it is now a complete Functions app root) + - name: Deploy to Azure Functions + uses: Azure/functions-action@v1 + with: + app-name: fn-info-embalse-prod + package: deploy + publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} diff --git a/README.md b/README.md index a28eb03..b3a7193 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,10 @@ docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.mi ### Añadir las variables de entorno -Dentro de la carpeta `functions`, crear un archivo `local.settings.json` con las siguientes variables de entorno: - -_/functions/local.settings.json_ - -```json -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "node", - "MONGODB_CONNECTION_STRING": "mongodb://localhost:27017/embalse-info" - } -} +Dentro de la carpeta `functions`, vamos crear un archivo `local.settings.json` a partir del de plantilla, ejecuta este comando desde el terminal (bash), desde el raiz del repo. + +```bash +cp functions/local.settings.template.json functions/local.settings.json ``` ### Arrancar las funciones de Azure @@ -134,4 +125,4 @@ Está pendiente conectar typeahead con el detalle del embalse. De momento, se pu http://localhost:3000/embalse/vinuela-la http://localhost:3000/embalse/villagudin -``` \ No newline at end of file +``` diff --git a/functions/host.json b/functions/host.json index 9df9136..295c84c 100644 --- a/functions/host.json +++ b/functions/host.json @@ -1,6 +1,12 @@ { "version": "2.0", "logging": { + "logLevel": { + "default": "Warning", + "Function": "Information", + "Host.Results": "Information", + "Host.Aggregator": "Warning" + }, "applicationInsights": { "samplingSettings": { "isEnabled": true, @@ -12,4 +18,4 @@ "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.*, 5.0.0)" } -} \ No newline at end of file +} diff --git a/functions/local.settings.template.json b/functions/local.settings.template.json new file mode 100644 index 0000000..57a2a58 --- /dev/null +++ b/functions/local.settings.template.json @@ -0,0 +1,11 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "node", + "MONGODB_CONNECTION_STRING": "mongodb://localhost:27017/embalse-info", + "APPLICATIONINSIGHTS_CONNECTION_STRING": "", + "SCRAPING_SCHEDULE": "0 * * * * *", + "ARCGIS_SCHEDULE": "40 * * * * *" + } +} diff --git a/functions/package.json b/functions/package.json index 3dc8859..0ef459e 100644 --- a/functions/package.json +++ b/functions/package.json @@ -3,12 +3,13 @@ "version": "0.0.1", "type": "module", "scripts": { + "prestart": "run-p clean type-check build", "start": "func start", - "watch": "tsc -w", - "build": "tsc", "clean": "rimraf dist", - "prestart": "run-p clean type-check build", - "type-check": "tsc --noEmit --preserveWatchOutput" + "type-check": "tsc --noEmit --preserveWatchOutput", + "build": "tsc", + "ci": "npm run clean && npm run type-check && npm run build", + "watch": "tsc -w" }, "dependencies": { "@azure/functions": "^4.0.0", diff --git a/functions/src/functions/arcgis-function.ts b/functions/src/functions/arcgis-function.ts index 83d7805..1e878f0 100644 --- a/functions/src/functions/arcgis-function.ts +++ b/functions/src/functions/arcgis-function.ts @@ -3,7 +3,7 @@ import { dbServer, embalsesRepository } from "@embalse-info/db"; export async function arcgisFunction( myTimer: Timer, - context: InvocationContext + context: InvocationContext, ): Promise { await dbServer.connect(process.env.MONGODB_CONNECTION_STRING as string); context.log("ArcGIS function executed at:", new Date().toISOString()); @@ -19,6 +19,8 @@ export async function arcgisFunction( } app.timer("arcgis-function", { + // Run once immediately when the Function App instance starts (deploy/restart/scale-out) + runOnStartup: true, retry: { strategy: "fixedDelay", delayInterval: { @@ -26,6 +28,6 @@ app.timer("arcgis-function", { }, maxRetryCount: 4, }, - schedule: "0 * * * * *", + schedule: process.env.ARCGIS_SCHEDULE ?? "0 0 3 * * Mon,Thu", // Twice per week handler: arcgisFunction, }); diff --git a/functions/src/functions/scraping-functions.ts b/functions/src/functions/scraping-functions.ts index 7ab68f4..d076f20 100644 --- a/functions/src/functions/scraping-functions.ts +++ b/functions/src/functions/scraping-functions.ts @@ -3,7 +3,7 @@ import { dbServer, embalsesRepository } from "@embalse-info/db"; export async function scrapingsFunction( myTimer: Timer, - context: InvocationContext + context: InvocationContext, ): Promise { await dbServer.connect(process.env.MONGODB_CONNECTION_STRING as string); context.log("Scrapings function executed at:", new Date().toISOString()); @@ -15,7 +15,7 @@ export async function scrapingsFunction( context.log(`Se han actualizado los embalses de la cuenca Mediterránea`); } else { context.log( - "No se han podido actualizar los embalses de la cuenca Mediterránea" + "No se han podido actualizar los embalses de la cuenca Mediterránea", ); } await dbServer.disconnect(); @@ -29,6 +29,6 @@ app.timer("scrapings-function", { }, maxRetryCount: 4, }, - schedule: "40 * * * * *", + schedule: process.env.SCRAPING_SCHEDULE ?? "0 0 */4 * * *", // Every 4 hours handler: scrapingsFunction, }); diff --git a/package-lock.json b/package-lock.json index ac12f28..fe336ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3050,6 +3050,24 @@ "resolved": "packages/db-model", "link": true }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/deep-eql": { "version": "5.0.2", "dev": true, @@ -4150,6 +4168,13 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "funding": [ @@ -5557,27 +5582,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-node/node_modules/debug": { - "version": "4.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/vite-node/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "dev": true, @@ -5661,27 +5665,6 @@ } } }, - "node_modules/vitest/node_modules/debug": { - "version": "4.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/webidl-conversions": { "version": "7.0.0", "license": "BSD-2-Clause",