Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0fcc9f0
feat: support for routes defined in the api
shreddedbacon Sep 30, 2025
405e2bd
refactor: adjust route endpoints to use less ids for inputs
shreddedbacon Oct 1, 2025
9947db6
test: use pr build image
shreddedbacon Oct 2, 2025
099905b
chore: setup route permissions in keycloak
shreddedbacon Oct 14, 2025
9d86f73
refactor: overhauled the api interactions to be a bit more friendly
shreddedbacon Oct 14, 2025
15e8e67
refactor: support for future autogenerated route sources
shreddedbacon Oct 15, 2025
71f5068
refactor: trim domains for spaces
shreddedbacon Oct 17, 2025
712bff9
refactor: yaml and autogen route cleanup
shreddedbacon Oct 17, 2025
89434c0
fix: only send populated payloads
shreddedbacon Oct 20, 2025
6a02542
test: add ansible test for api-routes
shreddedbacon Oct 23, 2025
551b01d
refactor: add limits for some features
shreddedbacon Oct 24, 2025
5e36596
feat: feature gate api routes by organization, disabled by default
shreddedbacon Oct 28, 2025
cc41e10
chore: adjust for copilot feedback
shreddedbacon Oct 29, 2025
cc08b7a
feat: feature gate autogenerated route configuration on project and e…
shreddedbacon Oct 29, 2025
1ffe8f8
chore: adjust for copilot feedback
shreddedbacon Oct 29, 2025
8afcc80
fix: broken autogenerated route config injection after copilot recomm…
shreddedbacon Oct 29, 2025
450487a
refactor: use fragment
shreddedbacon Oct 29, 2025
c6f662c
fix: add down migration to routes
shreddedbacon Oct 29, 2025
4b387b8
chore: magic number limits
shreddedbacon Oct 29, 2025
e9670ae
refactor: check limit and validation logic for annotations and altern…
shreddedbacon Oct 29, 2025
f99220c
chore: merge main and fix conflicts
shreddedbacon Oct 29, 2025
d555448
feat(refactor): dedicated autogenrated route configuration resolvers
shreddedbacon Oct 30, 2025
fb85fcd
test: fix quoted string in test file
shreddedbacon Oct 30, 2025
b916407
Revert "test: use pr build image"
rocketeerbkw Nov 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pipeline {
parallel {
stage ('1: run first test suite') {
steps {
sh script: "make -j$NPROC k3d/retest TESTS=[api,deploytarget,active-standby-kubernetes,features-kubernetes,features-kubernetes-2,features-variables] BRANCH_NAME=${SAFEBRANCH_NAME}", label: "Running first test suite on k3d cluster"
sh script: "make -j$NPROC k3d/retest TESTS=[api,api-routes,deploytarget,active-standby-kubernetes,features-kubernetes,features-kubernetes-2,features-variables] BRANCH_NAME=${SAFEBRANCH_NAME}", label: "Running first test suite on k3d cluster"
sh script: "pkill -f './local-dev/stern'", label: "Closing off test-suite-1 log after test completion"
}
}
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ UPSTREAM_TAG ?= latest
# latest is the most current release
# edge is the most current merged change
BUILD_DEPLOY_IMAGE_REPO = uselagoon/build-deploy-image
BUILD_DEPLOY_IMAGE_TAG ?= edge
BUILD_DEPLOY_IMAGE_TAG ?= pr-464

# UI_IMAGE_REPO and UI_IMAGE_TAG are an easy way to override the UI image used
# only works for installations where INSTALL_STABLE_CORE=false
Expand Down Expand Up @@ -462,7 +462,7 @@ JWT_VERSION = 6.2.0
STERN_VERSION = v2.6.1
CHART_TESTING_VERSION = v3.11.0
K3D_IMAGE = docker.io/rancher/k3s:v1.31.1-k3s1
TESTS = [nginx,api,features-kubernetes,bulk-deployment,features-kubernetes-2,features-variables,active-standby-kubernetes,tasks,drush,python,gitlab,github,bitbucket,services]
TESTS = [nginx,api,api-routes,features-kubernetes,bulk-deployment,features-kubernetes-2,features-variables,active-standby-kubernetes,tasks,drush,python,gitlab,github,bitbucket,services]
CHARTS_TREEISH = main
CHARTS_REPOSITORY = https://github.com/uselagoon/lagoon-charts.git
#CHARTS_REPOSITORY = ../lagoon-charts
Expand Down
94 changes: 94 additions & 0 deletions node-packages/commons/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export interface Project {
standbyRoutes: string;
storageCalc: number;
subfolder: string;
apiRoutes: any[];
autogeneratedRoutes: boolean;
autogeneratedRoutesPullrequests: boolean;
autogeneratedRoutePrefixes: string[]
autogeneratedPathRoutes: any[]
disableRequestVerification: boolean;
}

export interface Kubernetes {
Expand Down Expand Up @@ -697,9 +703,48 @@ export async function getEnvironmentByName(
autoIdle,
environmentType,
openshiftProjectName,
openshift {
...${deployTargetMinimalFragment}
}
updated,
created,
deleted,
envVariables {
name
value
scope
}
apiRoutes(source: API){
domain
service
alternativeNames{
domain
}
annotations{
key
value
}
pathRoutes{
toService
path
}
tlsAcme
insecure
hstsEnabled
hstsIncludeSubdomains
hstsPreload
hstsMaxAge
primary
source
type
}
autogeneratedRoutes
autogeneratedPathRoutes {
toService
fromService
path
}
disableRequestVerification
}
}
`);
Expand Down Expand Up @@ -733,6 +778,37 @@ export async function getEnvironmentByIdWithVariables(
value
scope
}
apiRoutes(source: API){
domain
service
alternativeNames{
domain
}
annotations{
key
value
}
pathRoutes{
toService
path
}
tlsAcme
insecure
hstsEnabled
hstsIncludeSubdomains
hstsPreload
hstsMaxAge
primary
source
type
}
autogeneratedRoutes
autogeneratedPathRoutes {
toService
fromService
path
}
disableRequestVerification
}
}
`);
Expand Down Expand Up @@ -846,6 +922,12 @@ interface GetOpenshiftInfoForProjectResult {
| 'standbyRoutes'
| 'storageCalc'
| 'subfolder'
| 'apiRoutes'
| 'autogeneratedRoutes'
| 'autogeneratedRoutesPullrequests'
| 'autogeneratedRoutePrefixes'
| 'autogeneratedPathRoutes'
| 'disableRequestVerification'
> & {
openshift: Pick<Kubernetes, keyof DeployTargetMinimalFragment>;
envVariables: Pick<EnvKeyValue, 'name' | 'scope' | 'value'>[];
Expand Down Expand Up @@ -885,6 +967,18 @@ export const getOpenShiftInfoForProject = (project: string): Promise<GetOpenshif
standbyRoutes
storageCalc
subfolder
apiRoutes{
domain
}
autogeneratedRoutes
autogeneratedRoutesPullrequests
autogeneratedRoutePrefixes
autogeneratedPathRoutes {
toService
fromService
path
}
disableRequestVerification
}
}
`);
Expand Down
158 changes: 147 additions & 11 deletions node-packages/commons/src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
deployTargetPullrequest,
deployTargetPromote
} from './deploy-tasks';
import { InternalEnvVariableScope, DeployData, DeployType, RemoveData } from './types';
import { InternalEnvVariableScope, DeployData, DeployType, RemoveData, RouteSource } from './types';
// @ts-ignore
import sha1 from 'sha1';
import crypto from 'crypto';
Expand All @@ -53,6 +53,30 @@ interface DeathHandler {
(msg: ConsumeMessage, error: Error): void;
}

interface AutogeneratedRouteConfiguration {
autogenerate?: {
enabled?: boolean;
allowPullRequests?: boolean;
insecure?: string;
prefixes?: string[];
'tls-acme'?: boolean;
ingressClass?: string;
disableRequestVerification?: boolean;
pathRoutes?: AutogeneratePathRoute[];
};
environment?: {
autogenerateRoutes?: boolean
autogeneratePathRoutes?: AutogeneratePathRoute[];
disableRequestVerification?: boolean;
}
}

interface AutogeneratePathRoute {
toService: string;
path: string;
fromService: string;
}

export let sendToLagoonTasks = function(
task: string,
payload?: any
Expand Down Expand Up @@ -528,11 +552,10 @@ export const getControllerBuildData = async function(deployTarget: any, deployDa
}

let deployment;
let environmentId;
const now = moment.utc();
const apiEnvironment = await getEnvironmentByName(branchName, lagoonProjectData.id, false);
const environmentId = apiEnvironment.environmentByName.id
try {
const now = moment.utc();
const apiEnvironment = await getEnvironmentByName(branchName, lagoonProjectData.id, false);
environmentId = apiEnvironment.environmentByName.id
deployment = await addDeployment(buildName,
"NEW",
now.format('YYYY-MM-DDTHH:mm:ss'),
Expand All @@ -551,10 +574,10 @@ export const getControllerBuildData = async function(deployTarget: any, deployDa
// encode some values so they get sent to the controllers nicely
const { routerPattern, appliedEnvVars: envVars } = await getEnvironmentsRouterPatternAndVariables(
lagoonProjectData,
environment.addOrUpdateEnvironment,
apiEnvironment.environmentByName,
deployTarget.openshift,
bulkId || null, bulkName || null, priority, buildVariables,
bulkType.Deploy
bulkType.Deploy,
)

let organization: any = null;
Expand Down Expand Up @@ -627,23 +650,105 @@ enum bulkType {
export const getEnvironmentsRouterPatternAndVariables = async function(
project: Pick<
Project,
'routerPattern' | 'sharedBaasBucket' | 'organization'
'routerPattern' |
'sharedBaasBucket' |
'organization' |
'autogeneratedRoutes' |
'autogeneratedRoutesPullrequests' |
'autogeneratedRoutePrefixes' |
'autogeneratedPathRoutes' |
'disableRequestVerification' |
'apiRoutes'
> & {
openshift: Pick<Kubernetes, 'routerPattern'>;
envVariables: Pick<EnvKeyValue, 'name' | 'value' | 'scope'>[];
},
environment: { envVariables: Pick<EnvKeyValue, 'name' | 'value' | 'scope'>[]},
environment: {
envVariables: Pick<EnvKeyValue, 'name' | 'value' | 'scope'>[]
apiRoutes,
autogeneratedRoutes
autogeneratedPathRoutes
disableRequestVerification
},
deployTarget: Pick<Kubernetes, 'name' | 'routerPattern' | 'sharedBaasBucketName'>,
bulkId: string | null,
bulkName: string | null,
buildPriority: number,
buildVariables: Array<{name: string, value: string}>,
bulkTask: bulkType
bulkTask: bulkType,
): Promise<{ routerPattern: string, appliedEnvVars: string }> {
type EnvKeyValueInternal = Pick<EnvKeyValue, 'name' | 'value'> & {
scope: EnvVariableScope | InternalEnvVariableScope;
}

/*
prepares the values to send to builds and tasks for consumption
this ensures the data is structured to match the one that the build expects when it receives the payload
this gets based64 encoded before being shipped. it is decoded during the build
*/
let apiRoutes = environment.apiRoutes
if (apiRoutes.length > 0) {
for (let i = 0; i < apiRoutes.length; i++) {
// remove any yaml/autogenerated sourced apiRoutes from the list
// these don't get send to builds from the api as they are configured by lagoon.yml
// or are created by the autogenerated route configuration
if ([RouteSource.YAML, RouteSource.AUTOGENERATED].includes(apiRoutes[i].source.toLowerCase())) {
// the environment queries have (source: API) on the environments apiRoutes request
// this check is just a fallback check
apiRoutes.splice(i, 1);
i--;
continue;
}
// the structure of annotations in the build-deploy-tool are `map[string]string`
// this converts the `key:value` type from the api to `map[string]string` for the build-deploy-tool to consume
let annotations = apiRoutes[i].annotations.reduce((acc, item) => {
acc[item.key] = item.value;
return acc;
}, {});
apiRoutes[i].annotations = annotations
// the structure of alternativeNames in the build-deploy-tool are `[]string`
// this converts to that type for the build-deploy-tool to consume
let alternativeNames = apiRoutes[i].alternativeNames.map(item => item.domain);
apiRoutes[i].alternativeNames = alternativeNames
}
}

/*
this handles creating the autogenerated route configuration if any autogenerated route settings
on the project or environment are set, this then gets setup in the required format
and base64 encoded before being sent to the build, it is decoded there
*/
let agPayload: AutogeneratedRouteConfiguration = {
autogenerate: {},
environment: {},
}
if (project.autogeneratedRoutes !== null) {
agPayload.autogenerate.enabled = project.autogeneratedRoutes
}
if (project.autogeneratedRoutesPullrequests !== null) {
agPayload.autogenerate.allowPullRequests = project.autogeneratedRoutesPullrequests
}
if (project.autogeneratedRoutePrefixes) {
agPayload.autogenerate.prefixes = project.autogeneratedRoutePrefixes
}
if (project.autogeneratedPathRoutes) {
agPayload.autogenerate.pathRoutes = project.autogeneratedPathRoutes
}
if (project.disableRequestVerification !== null) {
agPayload.autogenerate.disableRequestVerification = project.disableRequestVerification
}
if (environment.autogeneratedRoutes !== null) {
agPayload.environment.autogenerateRoutes = environment.autogeneratedRoutes
}
if (environment.autogeneratedPathRoutes) {
agPayload.environment.autogeneratePathRoutes = environment.autogeneratedPathRoutes
}
if (environment.disableRequestVerification !== null) {
// environment doesn't have a direct override for request validation disable, but as this is applied to a build
// we can just set the one that would apply normally to the project
agPayload.autogenerate.disableRequestVerification = environment.disableRequestVerification
}

// Single list of env vars that apply to an environment. Env vars from multiple
// sources (organization, project, enviornment, build vars, etc) are
// consolidated based on precedence.
Expand Down Expand Up @@ -711,6 +816,37 @@ export const getEnvironmentsRouterPatternAndVariables = async function(
});
}

// `LAGOON_API_ROUTES` is the successor to LAGOON_ROUTES_JSON. the build-deploy-tool will
// check if `LAGOON_ROUTES_JSON` is defined, then that will be used to prevent breaking deployments
// but will produce a build warning indicating that LAGOON_ROUTES_JSON will be deprecated in the future
// and to use API defined routes instead
if (apiRoutes.length > 0) {
applyIfNotExists({
name: "LAGOON_API_ROUTES",
value: encodeJSONBase64({routes: apiRoutes}),
scope: InternalEnvVariableScope.INTERNAL_SYSTEM
});
}
if (project.apiRoutes.length > 0) {
// if the project has any apiroutes defined, then we enforce cleanup of removed routes on any environments
applyIfNotExists({
name: "LAGOON_API_ROUTES_CLEANUP",
value: "true",
scope: InternalEnvVariableScope.INTERNAL_SYSTEM
});
}
if (Object.keys(agPayload.autogenerate).length || Object.keys(agPayload.environment).length) {
// if there is any autogenerated route configuration to send
// set that here
// @TODO: need to figure out a better way to handle the default autogenerate insecure handling
// agPayload.autogenerate.insecure = "Redirect"
applyIfNotExists({
name: "LAGOON_API_AUTOGENERATED_CONFIG",
value: encodeJSONBase64(agPayload),
scope: InternalEnvVariableScope.INTERNAL_SYSTEM
});
}

/*
* Normally scoped env vars.
*
Expand Down Expand Up @@ -1076,7 +1212,7 @@ export const getTaskProjectEnvironmentVariables = async (projectName: string, en
result.project,
environment.environmentById,
environment.environmentById.openshift,
null, null, priority, [], bulkType.Task // bulk deployments don't apply to tasks yet, but this is future proofing the function call
null, null, priority, [], bulkType.Task, // bulk deployments don't apply to tasks yet, but this is future proofing the function call
)
return appliedEnvVars
}
Expand Down
Loading
Loading