Skip to content

Commit

Permalink
Merge pull request #37 from OutSystems/development
Browse files Browse the repository at this point in the history
Merge `development` into `main`:: RMET-3619 :: Fix LifeTime Deployment Automation (#36)
  • Loading branch information
OS-martacarlos authored Sep 23, 2024
2 parents 0fa7621 + c38807e commit 3cb6a59
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 115 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/o11_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

name: (O11) Release Plugin

on:
workflow_dispatch:
workflow_call:
inputs:
plugin:
description: 'Name of the plugin in O11'
required: true
type: string
jobs:
deploy:
name: '🔌 Deploy plugin across LifeTime'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Install dependencies
run: npm install

- name: Tag Plugin with Version
run: npm run update:version --plugin="${{inputs.plugin}}" --lifetime=${{ secrets.LIFETIME}} --authentication='${{secrets.AUTOMATION_TOKEN}}'

- name: ⏳ Wait for tag version to propagate
run: sleep 20 # Waits for 20 seconds

- name: Deploying from DEV to TST
run: npm run deploy --plugin="${{inputs.plugin}}" --from=Development --to=Testing --lifetime=${{ secrets.LIFETIME }} --authentication='${{ secrets.AUTOMATION_TOKEN }}'

- name: ⏳ Wait for deployment to propagate
run: sleep 20 # Waits for 20 seconds

- name: Deploying from TST to PROD
run: npm run deploy --plugin="${{inputs.plugin}}" --from=Testing --to=Production --lifetime=${{ secrets.LIFETIME }} --authentication='${{ secrets.AUTOMATION_TOKEN }}'
37 changes: 6 additions & 31 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ on:
type: string

jobs:
change-extensibility:
change_extensibility:
uses: ./.github/workflows/o11_change_extensibility.yml
name: '✍🏻 Update O11 OML Extensibility'
secrets: inherit
Expand All @@ -38,36 +38,11 @@ jobs:
environment: enmobile11-dev.outsystemsenterprise.com

deploy_o11:
name: '🔌 Deploy plugin across LifeTime'
runs-on: ubuntu-latest
needs: change-extensibility
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Install dependencies
run: npm install

- name: Tag Plugin with Version
run: npm run update:version --plugin="InAppBrowser Plugin" --lifetime=${{ secrets.LIFETIME}} --authentication='${{secrets.AUTOMATION_TOKEN}}'

- name: ⏳ Wait for tag version to propagate
run: sleep 20 # Waits for 20 seconds

- name: Deploying from DEV to TST
run: npm run deploy --plugin="InAppBrowser Plugin" --from=Development --to=Testing --lifetime=${{ secrets.LIFETIME }} --authentication='${{ secrets.AUTOMATION_TOKEN }}'
uses: ./.github/workflows/o11_release.yml
secrets: inherit
with:
plugin: InAppBrowser Plugin

- name: ⏳ Wait for deployment to propagate
run: sleep 20 # Waits for 20 seconds

- name: Deploying from TST to PROD
run: npm run deploy --plugin="InAppBrowser Plugin" --from=Testing --to=Production --lifetime=${{ secrets.LIFETIME }} --authentication='${{ secrets.AUTOMATION_TOKEN }}'

deploy_odc:
name: '🔌 Update ODC OML Extensibility & Tenant Release'
uses: ./.github/workflows/odc_release.yml
Expand All @@ -89,7 +64,7 @@ jobs:
with:
tag: ${{ github.event.inputs.tag }}
environment: Production
plugin: InAppBrowserPlugin
plugin: InAppBrowser Plugin
notes: ${{ github.event.inputs.releaseNotes }}
pipelineID: ${{ needs.deploy_odc.outputs.pipelineID }}
forgeVersionO11: ${{ github.event.inputs.forgeVersionO11 }}
Expand Down
76 changes: 42 additions & 34 deletions scripts/deploy.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const utils = require('./utils');
const fs = require("fs");
const { Readable } = require('stream');

async function getLatestTagKey(base, pluginKey, auth){
let url = `${base}/applications/${pluginKey}/versions`;
Expand All @@ -17,35 +19,27 @@ async function getLatestTagKey(base, pluginKey, auth){
}
}

async function createDeploymentPlan(base, fromEnv, toEnv, pluginKey, auth) {
let url = `${base}/deployments`
let body = {
Notes: "Deployment triggered by github workflow",
SourceEnvironmentKey: fromEnv,
TargetEnvironmentKey: toEnv,
ApplicationOperations:[
{
ApplicationVersionKey: pluginKey
}
]
};
console.log(body)

async function createDeploymentPlan(base, toEnv, file, auth) {

let url = `${base}/environments/${toEnv}/deployment/`
let data = new Uint8Array(await new Response(file).arrayBuffer());
let blob = new Blob([data],{type:'application/octet-binary'});
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: auth
Authorization: auth,
'Content-Type': 'application/octet-stream'
},
body: JSON.stringify(body)
body: blob

})


if(response.ok && response.status == 201){
let key = await response.text()
console.log("Deployment Response:" + key);
return key;
}
let error = await response.text();
throw Error(`Couldn't create a binary deployment, with error: ${error}`)
}

async function startDeployment(base, deployKey, auth){
Expand All @@ -60,7 +54,19 @@ async function startDeployment(base, deployKey, auth){
})

if(response.ok && response.status == 202){
console.log("Deployment Started Successfully!");
console.log("Deployment Started Successfully 🚀!");
}else {
let res = await response.json();
console.log(res);
let url = `${base}/deployments/${deployKey}/status`;
let status = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: auth
}
})
throw Error (`!! Something while starting the deployment: status is ${status}, with error ${res}`);
}
}

Expand All @@ -78,27 +84,28 @@ async function isFinished(base, deployKey, auth) {
if(res.ok && res.status == 200){
let status = await res.json()
if(status.DeploymentStatus == 'running'){
console.log("Still running...");
console.log("Still running...");
return false
}
if(status.DeploymentStatus == 'aborted'){
throw Error ("!! Deployment aborted !!");
throw Error ("!! 🚨 Deployment aborted 🚨!!");
}
if(status.DeploymentStatus == 'finished_with_errors'){
throw Error ("!! Something went wrong with the deployment: finished with errors. Please check lifetime !!");
throw Error ("!! 🚨 Something went wrong with the deployment: finished with errors. Please check lifetime !!");
}

if(status.DeploymentStatus == 'finished_with_warnings'){
console.log("Finished with warnings");
console.log("🚧 Finished with warnings");
return true
}
if(status.DeploymentStatus == 'finished_successful'){
console.log("Finished with success");
console.log("Finished with success 🚀");
return true
}

}
throw Error ("!! Something went wrong with the request: " + await res.json());
let error = await res.json();
throw Error ("!! 🚨 Something went wrong with the request: " + error);
}


Expand All @@ -123,20 +130,23 @@ startDeploy(baseURL, fromEnvironment, toEnvironment, pluginSpaceName, basicAuthe
async function startDeploy(baseURL, fromEnvironment, toEnvironment, pluginSpaceName, auth){
let fromKey = await utils.getEnvironmentKey(baseURL, fromEnvironment, auth);
let toKey = await utils.getEnvironmentKey(baseURL, toEnvironment, auth);
console.log(`target key: ${toKey}`);

let pluginKey = await utils.getAppKey(baseURL, pluginSpaceName, auth);
console.log(`plugin key: ${pluginKey}`)
console.log(`plugin key: ${pluginKey}`);

let version = await utils.getLatestAppVersion(baseURL, pluginKey, auth);
console.log(`version to deploy: ${version}`)
console.log(`version to deploy: ${version}`);

let tagKey = await getLatestTagKey(baseURL, pluginKey, auth);
console.log(`tagged app key: ${tagKey}`)
console.log(`tagged app key: ${tagKey}`);

let downloadURL = await utils.requestDownloadURL(baseURL, fromKey, pluginKey, auth);
let file = await utils.download(downloadURL, auth);

let deploymentKey = await createDeploymentPlan(baseURL, fromKey, toKey, tagKey, auth);
console.log("deployment key: " + deploymentKey)
let deploymentKey = await createDeploymentPlan(baseURL, toKey, file, auth);
console.log(`deployment key ${deploymentKey}`);

//TODO: check for conflicts + slack message for approval if conflicts were found
await startDeployment(baseURL, deploymentKey, auth);

let intervalId = setInterval(async () => {
Expand All @@ -147,6 +157,4 @@ async function startDeploy(baseURL, fromEnvironment, toEnvironment, pluginSpaceN
clearInterval(intervalId);
}
}, 10000)


}
54 changes: 5 additions & 49 deletions scripts/download.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,20 @@
const utils = require('./utils');
const fs = require("fs");
const { finished } = require('stream/promises');
const { Readable } = require('stream');
const path = require("path");


const DOWNLOAD_FOLDER = "downloads";

async function download(url, fileName) {

const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: auth
}
})

if(!response.ok || response.status != 200) {
let error = await response.text();
console.error(error);
throw Error("Couldn't download file :(((.")
}
let file = response.body;

if (!fs.existsSync(DOWNLOAD_FOLDER)){
console.log("Create downloads folder: " + DOWNLOAD_FOLDER);
fs.mkdirSync(DOWNLOAD_FOLDER);
}

const destination = path.resolve(`./${DOWNLOAD_FOLDER}/${fileName}`);
const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
await finished(Readable.fromWeb(file).pipe(fileStream));
console.log(`Finifhed writing to ${destination}`);
}


async function downloadOAP(baseURL, pluginName, inEnv, version, auth) {
let envKey = await utils.getEnvironmentKey(baseURL, inEnv, auth);
let pluginKey = await utils.getAppKey(baseURL, pluginName, auth);

let downloadEndpoint = `${baseURL}/environments/${envKey}/applications/${pluginKey}/content`
const response = await fetch(downloadEndpoint, {
method: 'GET',
headers: {
Authorization: auth
}
})
console.log(version)
if(response.ok && response.status == 200){
let downloadInfo = await response.json()
await download(downloadInfo.url, `v${version}.oap`)
}
let downloadURL = await utils.requestDownloadURL(baseURL, envKey, pluginKey, auth);

let file = await utils.download(downloadURL, auth)
await utils.save(file, `v${version}.oap`);
}

let pluginSpaceName = process.env.npm_config_plugin;
let baseURL = process.env.npm_config_lifetime;
let auth = process.env.npm_config_authentication;
let environment = process.env.npm_config_environment;
let forgeVersion = process.env.npm_config_forge;
console.log(forgeVersion)

baseURL = `https://${baseURL}/lifetimeapi/rest/v2`;

downloadOAP(baseURL, pluginSpaceName, environment, forgeVersion, auth)
60 changes: 59 additions & 1 deletion scripts/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
const fs = require("fs");
const { finished } = require('stream/promises');
const { Readable } = require('stream');
const path = require("path");

const DOWNLOAD_FOLDER = "downloads";

async function getEnvironmentKey(base, env, auth){
let url = `${base}/environments`;

Expand Down Expand Up @@ -64,8 +71,59 @@ async function getLatestAppVersion(base, appKey, auth) {
throw Error ("Couldn't retrive app tag version.");
}

async function download(url, auth) {

const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: auth
}
})

if(!response.ok || response.status != 200) {
let error = await response.text();
console.error(error);
throw Error("Couldn't download file :(((.")
}
let file = response.body;

return file;
}

async function save(file, fileName) {
if (!fs.existsSync(DOWNLOAD_FOLDER)){
console.log("Create downloads folder: " + DOWNLOAD_FOLDER);
fs.mkdirSync(DOWNLOAD_FOLDER);
}

const destination = path.resolve(`./${DOWNLOAD_FOLDER}/${fileName}`);
const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
await finished(Readable.fromWeb(file).pipe(fileStream));
console.log(`Finifhed writing to ${destination}`);
}

async function requestDownloadURL(baseURL, envKey, pluginKey, auth) {
let downloadEndpoint = `${baseURL}/environments/${envKey}/applications/${pluginKey}/content`
const response = await fetch(downloadEndpoint, {
method: 'GET',
headers: {
Authorization: auth
}
})
if(response.ok && response.status == 200){
let downloadInfo = await response.json()
return downloadInfo.url
}

let error = await response.text();
throw Error(`Couldn't get download url, because of ${error}`);
}

module.exports = {
getAppKey,
getEnvironmentKey,
getLatestAppVersion
getLatestAppVersion,
download,
requestDownloadURL,
save
}

0 comments on commit 3cb6a59

Please sign in to comment.