Skip to content

Commit 17d469a

Browse files
committed
feat: support for routes defined in the api
1 parent 07b9cfa commit 17d469a

File tree

8 files changed

+961
-3
lines changed

8 files changed

+961
-3
lines changed

node-packages/commons/src/api.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,31 @@ export async function getEnvironmentByName(
700700
updated,
701701
created,
702702
deleted,
703+
apiRoutes{
704+
domain
705+
service
706+
alternativeNames{
707+
domain
708+
}
709+
annotations{
710+
key
711+
value
712+
}
713+
pathRoutes{
714+
toService
715+
path
716+
}
717+
tlsAcme
718+
insecure
719+
ingressClass
720+
hstsEnabled
721+
hstsIncludeSubdomains
722+
hstsPreload
723+
hstsMaxAge
724+
autogenerated
725+
primary
726+
source
727+
}
703728
}
704729
}
705730
`);

node-packages/commons/src/tasks.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ export const getControllerBuildData = async function(deployTarget: any, deployDa
529529

530530
let deployment;
531531
let environmentId;
532+
let apiRoutes;
532533
try {
533534
const now = moment.utc();
534535
const apiEnvironment = await getEnvironmentByName(branchName, lagoonProjectData.id, false);
@@ -544,6 +545,29 @@ export const getControllerBuildData = async function(deployTarget: any, deployDa
544545
sourceUser,
545546
sourceType,
546547
);
548+
549+
// get apiRoutes for the environment if any
550+
apiRoutes = apiEnvironment.environmentByName.apiRoutes
551+
if (apiRoutes.length > 0) {
552+
for (let i = 0; i < apiRoutes.length; i++) {
553+
// remove any yaml sourced apiRoutes from the list (these don't get send to builds from the api as they are configured by lagoon.yml)
554+
if (apiRoutes[i].source == "YAML") {
555+
delete apiRoutes[i]
556+
continue;
557+
}
558+
// the structure of annotations in the build-deploy-tool are `map[string]string`
559+
// this converts the `key:value` type from the api to `map[string]string` for the build-deploy-tool to consume
560+
let annotations = apiRoutes[i].annotations.reduce((acc, item) => {
561+
acc[item.key] = item.value;
562+
return acc;
563+
}, {});
564+
apiRoutes[i].annotations = annotations
565+
// the structure of alternativeNames in the build-deploy-tool are `[]string`
566+
// this converts to that type for the build-deploy-tool to consume
567+
let alternativeNames = apiRoutes[i].alternativeNames.map(item => item.domain);
568+
apiRoutes[i].alternativeNames = alternativeNames
569+
}
570+
}
547571
} catch (error) {
548572
logger.error(`Could not save deployment for project ${lagoonProjectData.id}. Message: ${error}`);
549573
}
@@ -554,7 +578,7 @@ export const getControllerBuildData = async function(deployTarget: any, deployDa
554578
environment.addOrUpdateEnvironment,
555579
deployTarget.openshift,
556580
bulkId || null, bulkName || null, priority, buildVariables,
557-
bulkType.Deploy
581+
bulkType.Deploy, apiRoutes
558582
)
559583

560584
let organization: any = null;
@@ -638,7 +662,8 @@ export const getEnvironmentsRouterPatternAndVariables = async function(
638662
bulkName: string | null,
639663
buildPriority: number,
640664
buildVariables: Array<{name: string, value: string}>,
641-
bulkTask: bulkType
665+
bulkTask: bulkType,
666+
apiRoutes: any
642667
): Promise<{ routerPattern: string, appliedEnvVars: string }> {
643668
type EnvKeyValueInternal = Pick<EnvKeyValue, 'name' | 'value'> & {
644669
scope: EnvVariableScope | InternalEnvVariableScope;
@@ -711,6 +736,19 @@ export const getEnvironmentsRouterPatternAndVariables = async function(
711736
});
712737
}
713738

739+
// `LAGOON_API_ROUTES` is the successor to LAGOON_ROUTES_JSON. the build-deploy-tool will
740+
// check if `LAGOON_ROUTES_JSON` is defined, then that will be used to prevent breaking deployments
741+
// but will produce a build warning indicating that LAGOON_ROUTES_JSON will be deprecated in the future
742+
// and to use API defined routes instead
743+
if (apiRoutes) {
744+
applyIfNotExists({
745+
name: "LAGOON_API_ROUTES",
746+
value: encodeJSONBase64({routes: apiRoutes}),
747+
scope: InternalEnvVariableScope.INTERNAL_SYSTEM
748+
});
749+
750+
}
751+
714752
/*
715753
* Normally scoped env vars.
716754
*
@@ -1076,7 +1114,8 @@ export const getTaskProjectEnvironmentVariables = async (projectName: string, en
10761114
result.project,
10771115
environment.environmentById,
10781116
environment.environmentById.openshift,
1079-
null, null, priority, [], bulkType.Task // bulk deployments don't apply to tasks yet, but this is future proofing the function call
1117+
null, null, priority, [], bulkType.Task, // bulk deployments don't apply to tasks yet, but this is future proofing the function call
1118+
null
10801119
)
10811120
return appliedEnvVars
10821121
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
exports.up = async function(knex) {
6+
route = await knex.schema.hasTable('routes');
7+
if (!route) {
8+
return knex.schema
9+
.createTable('routes', function (table) {
10+
table.increments('id').notNullable().primary();
11+
table.string('domain', 300).notNullable();
12+
table.integer('project').notNullable();
13+
table.integer('environment');
14+
table.string('service', 300);
15+
table.boolean('tls_acme').notNullable().defaultTo(1);
16+
table.enu('insecure', ['Allow', 'Redirect', 'None']).notNullable().defaultTo('Redirect');
17+
// table.string('ingress_class', 300);
18+
table.text('annotations');
19+
table.text('path_routes');
20+
table.string('monitoring_path', 300);
21+
table.boolean('hsts_enabled').notNullable().defaultTo(0);
22+
table.boolean('hsts_include_subdomains').notNullable().defaultTo(0);
23+
table.boolean('hsts_preload').notNullable().defaultTo(0);
24+
table.integer('hsts_max_age').defaultTo(3153600);
25+
table.boolean('autogenerated').notNullable().defaultTo(0);
26+
table.boolean('primary').notNullable().defaultTo(0);
27+
table.boolean('wildcard').notNullable().defaultTo(0);
28+
table.boolean('wildcard_apex').notNullable().defaultTo(0);
29+
table.boolean('disable_request_verification').notNullable().defaultTo(0);
30+
table.enu('verified', ['new','pending', 'running', 'error', 'verfied']).notNullable().defaultTo('new');
31+
table.json('verification').defaultTo('{}');
32+
table.enu('source', ['api','yaml']).notNullable().defaultTo('api');
33+
table.unique(['domain', 'project'], {indexName: 'route_project'});
34+
})
35+
.createTable('routes_alternate_domain', function (table) {
36+
table.increments('id').notNullable().primary();
37+
table.integer('rid');
38+
table.string('domain', 300).notNullable();
39+
table.json('verification').defaultTo('{}');
40+
table.unique(['domain', 'rid'], {indexName: 'alternate_domain_route'});
41+
})
42+
}
43+
else {
44+
return knex.schema
45+
}
46+
};
47+
48+
/**
49+
* @param { import("knex").Knex } knex
50+
* @returns { Promise<void> }
51+
*/
52+
exports.down = async function(knex) {
53+
return knex.schema
54+
};

services/api/src/resolvers.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,22 @@ const {
310310
getEnvVariablesByProjectEnvironmentName,
311311
} = require('./resources/env-variables/resolvers');
312312

313+
const {
314+
addRoute,
315+
deleteRoute,
316+
getRoutesByProjectId,
317+
getRoutesByEnvironmentId,
318+
getAlternateRoutesByRouteId,
319+
getRouteAnnotationsByRouteId,
320+
getPathRoutesByRouteId,
321+
addRouteAlternativeDomains,
322+
removeRouteAlternativeDomain,
323+
addRouteAnnotation,
324+
removeRouteAnnotation,
325+
addPathRoutesToRoute,
326+
removePathRouteFromRoute,
327+
} = require('./resources/routes/resolvers');
328+
313329
async function getResolvers() {
314330
let graphqlUpload;
315331
try {
@@ -456,6 +472,15 @@ async function getResolvers() {
456472
HARBOR: 'harbor',
457473
HISTORY: 'history',
458474
},
475+
RouteSource: {
476+
API: 'api',
477+
YAML: 'yaml',
478+
},
479+
RouteInsecure: {
480+
ALLOW: 'Allow',
481+
REDIRECT: 'Redirect',
482+
NONE: 'None',
483+
},
459484
Openshift: {
460485
projectUser: getProjectUser,
461486
token: getToken,
@@ -480,6 +505,7 @@ async function getResolvers() {
480505
publicKey: getProjectDeployKey,
481506
organizationDetails: getOrganizationByProject,
482507
retentionPolicies: getRetentionPoliciesByProjectId,
508+
apiRoutes: getRoutesByProjectId,
483509
},
484510
GroupInterface: {
485511
__resolveType(group) {
@@ -522,6 +548,12 @@ async function getResolvers() {
522548
facts: getFactsByEnvironmentId,
523549
openshift: getOpenshiftByEnvironmentId,
524550
kubernetes: getOpenshiftByEnvironmentId,
551+
apiRoutes: getRoutesByEnvironmentId,
552+
},
553+
Route: {
554+
alternativeNames: getAlternateRoutesByRouteId,
555+
annotations: getRouteAnnotationsByRouteId,
556+
pathRoutes: getPathRoutesByRouteId,
525557
},
526558
Organization: {
527559
groups: getGroupsByOrganizationId,
@@ -812,6 +844,14 @@ async function getResolvers() {
812844
deleteHistoryRetentionPolicy,
813845
addHistoryRetentionPolicyLink,
814846
removeHistoryRetentionPolicyLink,
847+
addRoute,
848+
addRouteAlternativeDomains,
849+
removeRouteAlternativeDomain,
850+
deleteRoute,
851+
addRouteAnnotation,
852+
removeRouteAnnotation,
853+
addPathRoutesToRoute,
854+
removePathRouteFromRoute,
815855
},
816856
Subscription: {
817857
backupChanged: backupSubscriber,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export type RouteAnnotation = {
2+
key: string;
3+
value: string;
4+
};
5+
6+
export type RouteAnnotations = RouteAnnotation[];
7+
8+
export function addAnnotation(
9+
annotations: RouteAnnotations,
10+
newKey: string,
11+
newValue: string
12+
): RouteAnnotations {
13+
const exists = annotations.some(
14+
(a) => a.key === newKey && a.value === newValue
15+
);
16+
17+
if (!exists) {
18+
return [...annotations, { key: newKey, value: newValue }];
19+
}
20+
21+
return annotations;
22+
}
23+
24+
export function removeAnnotation(
25+
annotations: RouteAnnotations,
26+
targetKey: string,
27+
): RouteAnnotations {
28+
return annotations.filter(
29+
(a) => !(a.key === targetKey)
30+
);
31+
}
32+
33+
export type PathRoute = {
34+
toService: string;
35+
path: string;
36+
};
37+
38+
export type PathRoutes = PathRoute[];
39+
40+
export function addServicePathRoute(
41+
pathRoutes: PathRoutes,
42+
newToService: string,
43+
newPath: string
44+
): PathRoutes {
45+
const exists = pathRoutes.some(
46+
(a) => a.toService === newToService && a.path === newPath
47+
);
48+
49+
if (!exists) {
50+
return [...pathRoutes, { toService: newToService, path: newPath }];
51+
}
52+
53+
return pathRoutes;
54+
}
55+
56+
export function removeServicePathRoute(
57+
pathRoutes: PathRoutes,
58+
targetToService: string,
59+
targetPath: string
60+
): PathRoutes {
61+
return pathRoutes.filter(
62+
(a) => !(a.toService === targetToService && a.path === targetPath)
63+
);
64+
}

0 commit comments

Comments
 (0)