azure-bake
Version:
Azure cloud deployment platform for both infrasturcture as code and software
244 lines • 12.2 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BakeRunner = void 0;
const core_1 = require("@azbake/core");
const ingredients_1 = require("./ingredients");
const colors_1 = require("colors");
const core_2 = require("@azbake/core");
const msRestNodeAuth = require("@azure/ms-rest-nodeauth");
const arm_resources_1 = require("@azure/arm-resources");
class BakeRunner {
constructor(bPackage, logger) {
this._customAuthToken = new Map();
this._package = bPackage;
this._logger = logger || new core_2.Logger([], bPackage.Environment.logLevel);
this._AuthCreds = {};
}
_loadBuiltIns() {
//register required ingredients
core_1.IngredientManager.Register('@azbake/ingredient-utils');
}
_executeBakeLoop(ingredientNames, finished, ctx) {
return __awaiter(this, void 0, void 0, function* () {
let recipe = ctx.Config.recipe;
let count = ingredientNames.length;
let foundErrors = false;
let executing = [];
for (let i = 0; i < count; ++i) {
let ingredientName = ingredientNames[i];
let ingredient = recipe.get(ingredientName) || {};
//check if we've already run this
let idx = finished.findIndex(x => x == ingredientName);
if (idx >= 0)
continue;
//check if igredient dependencies are all finished
let depsDone = true;
ingredient.dependsOn.forEach(dep => {
let idx = finished.findIndex(x => x == dep);
if (idx == -1) {
depsDone = false;
}
});
if (depsDone) {
//check if ingredient has a condition
if (ingredient.properties.condition) {
try {
let result = yield ingredient.properties.condition.valueAsync(ctx);
if (!result) {
let tmpLogger = new core_2.Logger(ctx.Logger.getPre().concat(ingredientName), ctx.Environment.logLevel);
tmpLogger.log("Condition check failed...skipping");
finished.push(ingredientName);
continue;
}
}
catch (e) {
const shouldIgnoreErrors = ingredient.properties.ignoreErrors || false;
if (shouldIgnoreErrors) {
this._logger.log((0, colors_1.red)("Error running condition check for " + ingredientName + " => " + e));
this._logger.log("ignoring above error and continuing");
}
else {
this._logger.error("Error running condition check for " + ingredientName + " => " + e);
foundErrors = true;
}
finished.push(ingredientName);
continue;
}
}
ctx.CustomAuthToken = this._customAuthToken.get(ingredientName) || null; // just pass through as Build will create a local ctx instance
let exec = ingredients_1.IngredientFactory.Build(ingredientName, ingredient, ctx);
if (exec) {
let promise = exec.Execute().then(() => { return ingredientName; }).catch((err) => {
const shouldIgnoreErrors = ingredient.properties.ignoreErrors || false;
if (shouldIgnoreErrors) {
this._logger.log((0, colors_1.red)(err));
this._logger.log("ignoring above error and continuing");
}
else {
this._logger.error(err);
foundErrors = true;
}
return ingredientName;
});
executing.push(promise);
}
else {
this._logger.error("Could not find ingredient type " + ingredient.properties.type + " for " + ingredientName);
foundErrors = true;
finished.push(ingredientName);
}
}
}
let results = yield Promise.all(executing);
results.forEach(r => finished.push(r));
if (foundErrors) {
throw new Error();
}
return ingredientNames.length != finished.length;
});
}
_bakeRegion(ctx) {
return __awaiter(this, void 0, void 0, function* () {
try {
var util = core_1.IngredientManager.getIngredientFunction("coreutils", ctx);
let rg_name = yield util.resource_group();
let region_name = ctx.Region.shortName;
if (ctx.Config.resourceGroup) {
let client = new arm_resources_1.ResourceManagementClient(ctx.AuthToken, ctx.Environment.authentication.subscriptionId);
let rgExists = false;
try {
let chkResult = yield client.resourceGroups.checkExistence(rg_name);
rgExists = chkResult.body;
}
catch (_a) { }
let tagGenerator = new core_1.TagGenerator(ctx);
if (!rgExists) {
ctx.Logger.log('Setting up resource group ' + (0, colors_1.cyan)(rg_name));
yield client.resourceGroups.createOrUpdate(rg_name, {
tags: tagGenerator.GenerateTags(),
location: region_name
});
}
else {
ctx.Logger.log('Updating resource group ' + (0, colors_1.cyan)(rg_name));
//for updates we still want to createOrUpdate so that tags can sync
//but we need to use the RG location in case it's different in later runs.
const rg = yield client.resourceGroups.get(rg_name);
yield client.resourceGroups.createOrUpdate(rg_name, {
tags: tagGenerator.GenerateTags(),
location: rg.location
});
}
}
let recipe = ctx.Config.recipe;
ctx.Logger.log('Baking recipe ' + (0, colors_1.cyan)(ctx.Config.name));
//we could build a DAG and execute that way, but we expect the number of recipes in a package to be small enough
//that a simple unoptimized loop through will work here
let ingredientNames = [];
recipe.forEach((igredient, name) => {
ingredientNames.push(name);
});
let finished = [];
let loopHasRemaining = true;
while (loopHasRemaining) {
try {
loopHasRemaining = yield this._executeBakeLoop(ingredientNames, finished, ctx);
}
catch (_b) {
throw new Error();
}
}
ctx.Logger.log('Finished baking');
return true;
}
catch (e) {
ctx.Logger.error(e);
return false;
}
});
}
login() {
return __awaiter(this, void 0, void 0, function* () {
this._loadBuiltIns();
this._logger.log("logging into azure...");
var result = yield this._package.Authenticate((auth) => __awaiter(this, void 0, void 0, function* () {
if (auth.skipAuth) {
this._logger.log("Skipping Azure login");
return true;
}
//TODO, new login does not support certificate SP login.
try {
this._AuthCreds = yield msRestNodeAuth
.loginWithServicePrincipalSecret(auth.serviceId, auth.secretKey, auth.tenantId);
}
catch (err) {
this._logger.error((0, colors_1.red)("login failed: " + err.message));
return false;
}
//check if any ingredients need access to the service principal credientals for custom auth
let recipe = this._package.Config.recipe;
let ctx = new core_2.DeploymentContext(this._AuthCreds, this._package, {}, this._logger);
for (const iterator of recipe) {
let name = iterator[0];
let ingredient = iterator[1];
let exec = ingredients_1.IngredientFactory.Build(name, ingredient, ctx);
const token = exec ? (yield exec.Auth(auth)) : null;
this._customAuthToken.set(name, token);
}
return true;
}));
return result;
});
}
bake(regions) {
return __awaiter(this, void 0, void 0, function* () {
if (this._package.Config.parallelRegions) {
let tasks = [];
regions.forEach(region => {
let ctx = new core_2.DeploymentContext(this._AuthCreds, this._package, region, new core_2.Logger(this._logger.getPre().concat(region.name), this._package.Environment.logLevel), undefined, null);
let task = this._bakeRegion(ctx);
tasks.push(task);
});
try {
let results = yield Promise.all(tasks);
let allResultsGood = true;
results.forEach(result => { if (!result)
allResultsGood = false; });
if (!allResultsGood) {
throw new Error('Not all regions deployed successfully');
}
}
catch (_a) {
throw new Error('Not all regions deployed successfully');
}
}
else {
let count = regions.length;
for (let i = 0; i < count; ++i) {
let region = regions[i];
let ctx = new core_2.DeploymentContext(this._AuthCreds, this._package, region, new core_2.Logger(this._logger.getPre().concat(region.name), this._package.Environment.logLevel), undefined, null);
try {
let r = yield this._bakeRegion(ctx);
if (!r) {
throw new Error('Not all regions deployed successfully'); //force failed result code
}
}
catch (err) {
throw err;
}
}
}
});
}
}
exports.BakeRunner = BakeRunner;
//# sourceMappingURL=bake-runner.js.map