Skip to content
This repository was archived by the owner on Apr 13, 2020. It is now read-only.

Commit e13aeb9

Browse files
committed
Default Routing for isDefault Ring
- When a ring is marked as `isDefault: true`, `spk hld reconcile` will now generate an additional IngressRoute and Middleware. - The IngressRoute will not have a `Ring` route match rule - The IngressRoute points to the same backend service as its ringed counterpart. - Only one ring can be marked `isDefault: true` -- validation is run at `spk hld reconcile` execute time, throwing an error if more than one `isDefault`. - Refactored IngressRoute tests - Added compatibility configuration for eslint and prettier. closes microsoft/bedrock#1084
1 parent be744de commit e13aeb9

14 files changed

+649
-252
lines changed

.eslintrc.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
module.exports = {
22
root: true,
3+
env: {
4+
node: true
5+
},
36
parser: "@typescript-eslint/parser",
47
plugins: ["@typescript-eslint"],
58
extends: [
69
"eslint:recommended",
710
"plugin:@typescript-eslint/eslint-recommended",
8-
"plugin:@typescript-eslint/recommended"
11+
"plugin:@typescript-eslint/recommended",
12+
"prettier",
13+
"prettier/@typescript-eslint"
914
]
1015
};

jest.config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module.exports = {
2-
preset: 'ts-jest',
3-
testEnvironment: 'node',
4-
};
2+
preset: "ts-jest",
3+
testEnvironment: "node",
4+
rootDir: "src"
5+
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"build-cmd-docs": "ts-node tools/generateDoc.ts",
1111
"lint": "eslint 'src/**/*.ts{,x}'",
1212
"lint-fix": "tslint --fix 'src/**/*.ts{,x}'",
13-
"test": "jest --rootDir src --reporters=jest-junit --reporters=default --coverage --coverageReporters=cobertura --coverageReporters=html",
13+
"test": "jest --reporters=jest-junit --reporters=default --coverage --coverageReporters=cobertura --coverageReporters=html",
1414
"test-watch": "jest --watchAll",
1515
"postinstall": "cd node_modules/azure-devops-node-api && git apply ../../patches/001-azure-devops-node.patch || true",
1616
"test-coverage-html": "jest --coverage --coverageReporters=html"
@@ -32,6 +32,7 @@
3232
"@typescript-eslint/eslint-plugin": "^2.23.0",
3333
"@typescript-eslint/parser": "^2.23.0",
3434
"eslint": "^6.8.0",
35+
"eslint-config-prettier": "^6.10.0",
3536
"husky": ">=1",
3637
"jest": "^24.9.0",
3738
"jest-junit": "^9.0.0",

src/commands/hld/reconcile.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from "path";
22
import { create as createBedrockYaml } from "../../lib/bedrockYaml";
33
import { disableVerboseLogging, enableVerboseLogging } from "../../logger";
44
import { BedrockFile, BedrockServiceConfig } from "../../types";
5+
import * as reconcile from "./reconcile";
56
import {
67
addChartToRing,
78
checkForFabrikate,
@@ -14,13 +15,12 @@ import {
1415
execAndLog,
1516
execute,
1617
getFullPathPrefix,
17-
ReconcileDependencies,
1818
normalizedName,
19+
ReconcileDependencies,
1920
reconcileHld,
2021
testAndGetAbsPath,
2122
validateInputs
2223
} from "./reconcile";
23-
import * as reconcile from "./reconcile";
2424

2525
beforeAll(() => {
2626
enableVerboseLogging();

src/commands/hld/reconcile.ts

Lines changed: 99 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
/* eslint-disable @typescript-eslint/camelcase */
33
import child_process from "child_process";
44
import commander from "commander";
5-
import { writeFileSync } from "fs";
5+
import fs from "fs";
66
import yaml from "js-yaml";
77
import path from "path";
88
import process from "process";
99
import shelljs, { TestOptions } from "shelljs";
1010
import { Bedrock } from "../../config";
1111
import { assertIsStringWithContent } from "../../lib/assertions";
12+
import * as bedrock from "../../lib/bedrockYaml";
1213
import { build as buildCmd, exit as exitCmd } from "../../lib/commandBuilder";
1314
import { generateAccessYaml } from "../../lib/fileutils";
1415
import { tryGetGitOrigin } from "../../lib/gitutils";
1516
import * as dns from "../../lib/net/dns";
16-
import { TraefikIngressRoute } from "../../lib/traefik/ingress-route";
17-
import { TraefikMiddleware } from "../../lib/traefik/middleware";
17+
import * as ingressRoute from "../../lib/traefik/ingress-route";
18+
import * as middleware from "../../lib/traefik/middleware";
1819
import { logger } from "../../logger";
1920
import { BedrockFile, BedrockServiceConfig } from "../../types";
2021
import decorator from "./reconcile.decorator.json";
@@ -57,7 +58,7 @@ const exec = async (cmd: string, pipeIO = false): Promise<ExecResult> => {
5758

5859
export interface ReconcileDependencies {
5960
exec: typeof execAndLog;
60-
writeFile: typeof writeFileSync;
61+
writeFile: typeof fs.writeFileSync;
6162
getGitOrigin: typeof tryGetGitOrigin;
6263
generateAccessYaml: typeof generateAccessYaml;
6364
createAccessYaml: typeof createAccessYaml;
@@ -105,6 +106,7 @@ export const execute = async (
105106
);
106107

107108
const bedrockConfig = Bedrock(absBedrockPath);
109+
bedrock.validateRings(bedrockConfig);
108110

109111
logger.info(
110112
`Attempting to reconcile HLD with services tracked in bedrock.yaml`
@@ -123,7 +125,7 @@ export const execute = async (
123125
exec: execAndLog,
124126
generateAccessYaml,
125127
getGitOrigin: tryGetGitOrigin,
126-
writeFile: writeFileSync
128+
writeFile: fs.writeFileSync
127129
};
128130

129131
await reconcileHld(
@@ -238,22 +240,23 @@ export const reconcileHld = async (
238240
normalizedSvcName
239241
);
240242

241-
const ringsToCreate = Object.keys(managedRings).map(ring => {
242-
const normalizedRingName = normalizedName(ring);
243-
return {
244-
normalizedRingName,
245-
normalizedRingPathInHld: path.join(
246-
normalizedSvcPathInHld,
247-
normalizedRingName
248-
)
249-
};
250-
});
243+
const ringsToCreate = Object.entries(managedRings).map(
244+
([ring, { isDefault }]) => {
245+
const normalizedRingName = normalizedName(ring);
246+
return {
247+
isDefault: !!isDefault,
248+
normalizedRingName,
249+
normalizedRingPathInHld: path.join(
250+
normalizedSvcPathInHld,
251+
normalizedRingName
252+
)
253+
};
254+
}
255+
);
251256

252257
// Will only loop over _existing_ rings in bedrock.yaml - does not cover the deletion case, ie: i remove a ring from bedrock.yaml
253-
for (const {
254-
normalizedRingName,
255-
normalizedRingPathInHld
256-
} of ringsToCreate) {
258+
for (const ring of ringsToCreate) {
259+
const { normalizedRingName, normalizedRingPathInHld, isDefault } = ring;
257260
// Create the ring in the service.
258261
await dependencies.createRingComponent(
259262
dependencies.exec,
@@ -302,7 +305,8 @@ export const reconcileHld = async (
302305
normalizedRingPathInHld,
303306
normalizedSvcName,
304307
normalizedRingName,
305-
ingressVersionAndPath
308+
ingressVersionAndPath,
309+
isDefault
306310
);
307311

308312
// Create Ingress Route.
@@ -312,7 +316,8 @@ export const reconcileHld = async (
312316
serviceConfig,
313317
middlewares,
314318
normalizedRingName,
315-
ingressVersionAndPath
319+
ingressVersionAndPath,
320+
isDefault
316321
);
317322
}
318323
}
@@ -369,68 +374,120 @@ export const createAccessYaml = async (
369374
writeAccessYaml(absRepositoryPathInHldPath, originUrl);
370375
};
371376

372-
const createIngressRouteForRing = (
377+
type MiddlewareMap<T = Partial<ReturnType<typeof middleware.create>>> = {
378+
ringed: T;
379+
default?: T;
380+
};
381+
382+
export const createIngressRouteForRing = (
373383
ringPathInHld: string,
374384
serviceName: string,
375385
serviceConfig: BedrockServiceConfig,
376-
middlewares: TraefikMiddleware,
386+
middlewares: MiddlewareMap,
377387
ring: string,
378-
ingressVersionAndPath: string
379-
): void => {
388+
ingressVersionAndPath: string,
389+
ringIsDefault = false
390+
): ReturnType<typeof ingressRoute.create>[] => {
380391
const staticComponentPathInRing = path.join(ringPathInHld, "static");
381392
const ingressRoutePathInStaticComponent = path.join(
382393
staticComponentPathInRing,
383394
"ingress-route.yaml"
384395
);
385-
const ingressRoute = TraefikIngressRoute(
396+
397+
// Push the default ingress route with ring header
398+
const ingressRoutes = [];
399+
const ringedRoute = ingressRoute.create(
386400
serviceName,
387401
ring,
388402
serviceConfig.k8sBackendPort,
389403
ingressVersionAndPath,
390404
{
405+
isDefault: false,
391406
k8sBackend: serviceConfig.k8sBackend,
392407
middlewares: [
393-
middlewares.metadata.name,
408+
middlewares.ringed.metadata?.name,
394409
...(serviceConfig.middlewares ?? [])
395-
]
410+
].filter((e): e is NonNullable<typeof e> => !!e)
396411
}
397412
);
413+
ingressRoutes.push(ringedRoute);
414+
415+
// If ring isDefault, scaffold an additional ingress route without the ring
416+
// header -- i.e with an empty string ring name
417+
if (ringIsDefault) {
418+
const defaultRingRoute = ingressRoute.create(
419+
serviceName,
420+
ring,
421+
serviceConfig.k8sBackendPort,
422+
ingressVersionAndPath,
423+
{
424+
isDefault: ringIsDefault,
425+
k8sBackend: serviceConfig.k8sBackend,
426+
middlewares: [
427+
middlewares.default?.metadata?.name,
428+
...(serviceConfig.middlewares ?? [])
429+
].filter((e): e is NonNullable<typeof e> => !!e)
430+
}
431+
);
432+
ingressRoutes.push(defaultRingRoute);
433+
}
398434

399-
const routeYaml = yaml.safeDump(ingressRoute, {
400-
lineWidth: Number.MAX_SAFE_INTEGER
401-
});
435+
// serialize to routes to yaml separately and join them with `---` to specify
436+
// multiple yaml documents in a single string
437+
const routeYaml = ingressRoutes
438+
.map(str => {
439+
return yaml.safeDump(str, {
440+
lineWidth: Number.MAX_SAFE_INTEGER
441+
});
442+
})
443+
.join("\n---\n");
402444

403445
logger.info(
404446
`Writing IngressRoute YAML to '${ingressRoutePathInStaticComponent}'`
405447
);
406448

407-
writeFileSync(ingressRoutePathInStaticComponent, routeYaml);
449+
fs.writeFileSync(ingressRoutePathInStaticComponent, routeYaml);
450+
return ingressRoutes;
408451
};
409452

410-
const createMiddlewareForRing = (
453+
export const createMiddlewareForRing = (
411454
ringPathInHld: string,
412455
serviceName: string,
413456
ring: string,
414-
ingressVersionAndPath: string
415-
): TraefikMiddleware => {
457+
ingressVersionAndPath: string,
458+
ringIsDefault = false
459+
): MiddlewareMap => {
416460
// Create Middlewares
417461
const staticComponentPathInRing = path.join(ringPathInHld, "static");
418462
const middlewaresPathInStaticComponent = path.join(
419463
staticComponentPathInRing,
420464
"middlewares.yaml"
421465
);
422466

423-
const middlewares = TraefikMiddleware(serviceName, ring, [
424-
ingressVersionAndPath
425-
]);
426-
const middlewareYaml = yaml.safeDump(middlewares, {
427-
lineWidth: Number.MAX_SAFE_INTEGER
428-
});
467+
// Create the standard ringed middleware as well as one without the ring in
468+
// the name if the ring isDefault
469+
const middlewares = {
470+
ringed: middleware.create(serviceName, ring, [ingressVersionAndPath]),
471+
default: ringIsDefault
472+
? middleware.create(serviceName, "", [ingressVersionAndPath])
473+
: undefined
474+
};
475+
476+
// Serialize all the middlewares to yaml separately and join the strings with
477+
// '---' to specify multiple yaml docs in a single string
478+
const middlewareYaml = Object.values(middlewares)
479+
.filter((e): e is NonNullable<typeof e> => !!e)
480+
.map(str =>
481+
yaml.safeDump(str, {
482+
lineWidth: Number.MAX_SAFE_INTEGER
483+
})
484+
)
485+
.join("\n---\n");
429486

430487
logger.info(
431488
`Writing Middlewares YAML to '${middlewaresPathInStaticComponent}'`
432489
);
433-
writeFileSync(middlewaresPathInStaticComponent, middlewareYaml);
490+
fs.writeFileSync(middlewaresPathInStaticComponent, middlewareYaml);
434491

435492
return middlewares;
436493
};

0 commit comments

Comments
 (0)