UNPKG

azure-bake

Version:

Azure cloud deployment platform for both infrasturcture as code and software

244 lines 12.2 kB
"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