From eb588d72d5c1271338d2f772560db42bfe11a5a2 Mon Sep 17 00:00:00 2001 From: Hadrien Froger Date: Wed, 7 Feb 2024 11:01:49 +0000 Subject: [PATCH] ci: scripts to redeploy in developments --- .github/workflows/backup.yml | 310 +++++++++++++++++++++++++++++ .github/workflows/redeploy-dev.yml | 27 +++ 2 files changed, 337 insertions(+) create mode 100644 .github/workflows/backup.yml create mode 100644 .github/workflows/redeploy-dev.yml diff --git a/.github/workflows/backup.yml b/.github/workflows/backup.yml new file mode 100644 index 0000000..83a4f81 --- /dev/null +++ b/.github/workflows/backup.yml @@ -0,0 +1,310 @@ +jpsType: update +jpsVersion: '1.7.4' + +name: Scripts/DailyTask +id: daily-task +description: + short: Sysadmin Actions for jelastic account +categories: + - apps/platforms +ssl: true +ha: false +settings: + fields: + - name: RESTIC_REPOSITORY + type: string + caption: Host+bucket+region for the S3 + - name: AWS_ACCESS_KEY_ID + type: string + caption: Key ID for S3 + - name: AWS_SECRET_ACCESS_KEY + type: string + caption: Secret for S3 + - name: RESTIC_PASSWORD + type: string + caption: Key used for restic encryption + - name: AWS_DEFAULT_REGION + type: string + caption: Default Region + default: 'ch-dk-2' + - name: TAG + type: string + caption: Tag the backup + default: 'daily' + +onInstall: + - type: "javascript" + script: + - 'var resp = jelastic.environment.control.GetEnvs(appid, session);' + - 'if(resp.result != 0) return {"result": 1, "message": "ERROR"};' + - 'var nodes = (resp.infos || []).filter(function(env) { + return (env.env.status || 0) === 1; + }).reduce(function(acc, curr) { + acc[curr.env.envName] = { + raw: curr, + nodes: curr.nodes.filter(function(nd) { + if(nd.nodeGroup === "cp" && curr.nodes.find(function(nd2) { return nd2.nodeGroup === "storage"; })) { + return false; + } + return (typeof nd.customitem !== "undefined") && + (typeof nd.customitem.dockerVolumes !== "undefined") && + (nd.customitem.dockerVolumes.length > 0) && + ["cp", "bl", "sqldb", "storage"].includes(nd.nodeGroup); + }).map(function(nd) { + return { + nodeGroup: nd.nodeGroup, + volumes: nd.customitem.dockerVolumes, + id: nd.id, + ip: nd.intIP, + port: nd.port + }; + }) + }; + return acc; + }, {})' + - 'var result = Object.keys(nodes).map(function(envName) { + var nd = nodes[envName]; + nd.envName = envName; + return nd; + })' + - 'return {"result": 0,"environments": JSON.stringify(result)}' + + - setGlobals: + - ENVIRONMENTS: ${response.environments} + + - type: javascript + environments: ${globals.ENVIRONMENTS} + script: + - 'var installRestic = function(envName, nodeGroups){ + var results = nodeGroups.map(function(nodeGroup) { + var resp = jelastic.environment.control.ExecCmdByGroup( + envName, + session, + nodeGroup, + JSON.stringify([ + { + "command": "which restic >/dev/null || { which dnf >/dev/null && { dnf install -y epel-release; dnf install -y restic; } || { which apk >/dev/null && apk add restic; } || { which yum >/dev/null && { yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/copart/restic/repo/epel-7/copart-restic-epel-7.repo; yum-config-manager --enable copr:copr.fedorainfracloud.org:copart:restic; yum -y install restic; yum-config-manager --disable copr:copr.fedorainfracloud.org:copart:restic; } ; } ; }" + } + ]) + ); + return resp.result === 0; + }); + return results.filter(Boolean).length === results.length; + }' + - 'var environments = JSON.parse(getParam("environments"))' + - 'var failedInstall = environments.filter(function(environment) { + var nodes = environment.nodes; + var nodeGroups = nodes.map(function(nd) { return nd.nodeGroup }); + return !installRestic(environment.envName, nodeGroups) + })' + - 'var outputMessage = failedInstall.length > 0 ? + ( + "Fail to install Restic. Check environments " + failedInstall.map(function(environment) { + return "`" + environment.envName + "`"; + }).join(", ") + ) : "OK"' + - 'return {result: 0, outputMessage: outputMessage }' + - setGlobals: + - OUTPUT_RESTIC_INSTALL: ${response.outputMessage} + - type: javascript + environments: ${globals.ENVIRONMENTS} + script: + - 'var backupContainer = function(envName, nodeGroup, directories, topology){ + var tag = "${settings.TAG}"; + var repo = "s3:${settings.RESTIC_REPOSITORY}/" + envName + "-" + nodeGroup; + var keyId = "${settings.AWS_ACCESS_KEY_ID}"; + var keySecret = "${settings.AWS_SECRET_ACCESS_KEY}"; + var resticPassword = "${settings.RESTIC_PASSWORD}"; + var defaultRegion = "${settings.AWS_DEFAULT_REGION}"; + if(topology) { + jelastic.environment.control.ExecCmdByGroup( + envName, + session, + nodeGroup, + JSON.stringify([ + { + "command": "mkdir -p /tmp/topology", + "params": "" + } + ]) + ); + directories.push("/tmp/topology"); + jelastic.environment.file.write( + envName, + session, + "/tmp/topology/topology.json", + JSON.stringify(topology), + undefined, + nodeGroup + ); + } + var resp = jelastic.environment.control.ExecCmdByGroup( + envName, + session, + nodeGroup, + JSON.stringify([ + { + "command": "export RESTIC_REPOSITORY=\""+repo+"\"", + "params": "" + }, + { + "command": "export AWS_ACCESS_KEY_ID=\""+keyId+"\"", + "params": "" + }, + { + "command": "export AWS_SECRET_ACCESS_KEY=\""+keySecret+"\"", + "params": "" + }, + { + "command": "export RESTIC_PASSWORD=\""+resticPassword+"\"", + "params": "" + }, + { + "command": "export AWS_DEFAULT_REGION=\""+defaultRegion+"\"", + "params": "" + }, + { + "command": "if ! restic snapshots >/dev/null 2>&1; then restic init; fi", + "params": "" + }, + { + "command": "restic backup --tag "+ tag +" " + directories.filter(function(dir) { + return !dir.endsWith("vendor") && !dir.endsWith("node_modules"); + }).join(" "), + "params": "" + } + ]) + ); + return resp.result === 0; + }' + - 'var backupDatabase = function(envName){ + var resp = jelastic.environment.control.ExecCmdByGroup( + envName, + session, + "sqldb", + JSON.stringify([ + { + "command": "mkdir -p /root/dump", + "params": "" + }, + { + "command": "cd /root/dump", + "params": "" + }, + { + "command": "databases=$(psql --username $POSTGRES_USER postgres -qAt -c \"SELECT datname FROM pg_database WHERE NOT datistemplate\")", + "params": "" + }, + { + "command": "for db in $databases; do pg_dump --username $POSTGRES_USER -Fp $db > /root/dump/$db.sql; done", + "params": "" + } + ]) + ); + return resp.result == 0; + }' + - 'var environments = JSON.parse(getParam("environments"))' + - 'var backups = environments.map(function(environment) { + var nodes = environment.nodes; + var raw = environment.raw; + var envName = environment.envName; + var nodeGroups = nodes.map(function(nd) { return nd.nodeGroup }); + var backupProcess = []; + var hasStorage = nodeGroups.includes("storage"); + if(nodeGroups.includes("sqldb")) { + var matchNode = nodes.find(function(nd) { return nd.nodeGroup == "sqldb";}); + backupProcess.push(backupContainer(envName, "sqldb", ["/root/dump"], undefined)); + } + if(nodeGroups.includes("cp")) { + var matchNode = nodes.find(function(nd) { return nd.nodeGroup == "cp";}); + backupProcess.push(backupContainer(envName, "cp", matchNode.volumes, hasStorage ? undefined : raw)); + } + if(hasStorage) { + var matchNode = nodes.find(function(nd) { return nd.nodeGroup == "storage";}); + backupProcess.push(backupContainer(envName, "storage", matchNode.volumes, raw)); + } + if(nodeGroups.includes("bl")) { + var matchNode = nodes.find(function(nd) { return nd.nodeGroup == "bl";}); + backupProcess.push(backupContainer(envName, "bl", matchNode.volumes, undefined)); + } + return [ + envName, + backupProcess.length > 0 && backupProcess.filter(Boolean).length === backupProcess.length + ]; + })' + - 'var outputMessage = backups.map(function(backupInfo) { + return " * " + backupInfo[0] + ": " + (backupInfo[1] ? "✅" : "⚠️") + }).join("\n")' + - 'return {"result": 0, outputMessage: outputMessage}' + + - setGlobals: + - OUTPUT_BACKUP: ${response.outputMessage} + + - type: javascript + environments: ${globals.ENVIRONMENTS} + script: + - 'var fetchBilling = function(targetAppId, envName) { + var yesterdayStart = new Date(); + yesterdayStart.setDate(yesterdayStart.getDate() - 1); + yesterdayStart.setUTCHours(0,0,0,0); + yesterdayStart = yesterdayStart.toISOString(); + var yesterdayEnd = new Date(); + yesterdayEnd.setDate(yesterdayEnd.getDate() - 1); + yesterdayEnd.setUTCHours(23,59,59,999); + yesterdayEnd = yesterdayEnd.toISOString(); + var billingHistory = jelastic.billing.account.GetExtendedAccountBillingHistoryByPeriod( + appid, + session, + yesterdayStart, + yesterdayEnd, + targetAppId + ); + if(!billingHistory || billingHistory.result !== 0 || billingHistory.object.envs.length === 0) { + return null; + } + var envBillingHistory = billingHistory.object.envs[0][envName]; + var cloudlets = (envBillingHistory.cloudlets.flexible.cost || 0) + (envBillingHistory.cloudlets.fixed.cost || 0); + var storage = envBillingHistory.storage.total.cost || 0; + var ips = envBillingHistory.ips.cost || 0; + var ssl = envBillingHistory.ssl.cost || 0; + return { + "total": (cloudlets + storage + ips + ssl).toFixed(2), + "ips": ips.toFixed(2), + "cloudlets": cloudlets.toFixed(2), + "storage": storage.toFixed(2), + "ssl": ssl.toFixed(2) + }; + }' + - 'var environments = JSON.parse(getParam("environments"))' + - 'var billings = environments.map(function(environment) { + var billingHistory = fetchBilling(environment.raw.env.appid, envName); + return [ + "Yesterday Billing " + (billingHistory ? billingHistory.total + "CHF" : "unknown"), + "_Yesterday Billing Details_", + (billingHistory ? Object.keys(billingHistory).filter(function(k) { return k !== "total"}).map(function(k) { + var value = billingHistory[k]; + return " * __"+ k + "__: " + (value === "0" ? "free" : value + "CHF") + }).join("\n") : "unknown") + ].join("\n\n"); + }' + - 'var outputMessage = billings.join("\n")' + - 'return {"result": 0, outputMessage: outputMessage}' + - setGlobals: + - OUTPUT_BILLING: ${response.outputMessage} + + - return: + type: success + message: | + # Jelastic Daily Review + ## Backup Status + **▪️ Backup are installed** + + ${globals.OUTPUT_RESTIC_INSTALL} + + **▪️ Backup are done** + + ${globals.OUTPUT_BACKUP} + + ## Billing status + ${globals.OUTPUT_BILLING} + diff --git a/.github/workflows/redeploy-dev.yml b/.github/workflows/redeploy-dev.yml new file mode 100644 index 0000000..cb39236 --- /dev/null +++ b/.github/workflows/redeploy-dev.yml @@ -0,0 +1,27 @@ +name: Redploy Decidim Image in Jelastic Infra + +on: + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + container: + image: mwienk/jelastic-cli + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Redeploy container in Jelastic + env: + JELASTIC_LOGIN: ${{ secrets.JELASTIC_LOGIN }} + JELASTIC_PASSWORD: ${{ secrets.JELASTIC_PASSWORD }} + JELASTIC_HOSTER: ${{ secrets.JELASTIC_HOSTER }} + ENVNAME: ${{ secrets.ENVNAME }} + NODE_GROUP: ${{ secrets.NODE_GROUP }} # Default to cp if not set + TAG: ${{ secrets.TAG }} # Default to latest if not set + USE_EXISTING_VOLUME: ${{ secrets.USE_EXISTING_VOLUME }} # Default to true if not set + run: | + /root/jelastic/users/authentication/signin --login $JELASTIC_LOGIN --password $JELASTIC_PASSWORD --platformUrl $JELASTIC_HOSTER + /root/jelastic/environment/control/redeploycontainersbygroup --envName $ENVNAME --nodeGroup ${NODE_GROUP:-cp} --tag ${TAG:-latest} --useExistingVolumes ${USE_EXISTING_VOLUME:-true} \ No newline at end of file