From c830b6e34aff4d0661fca084482ef0aa9f2b3aa9 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 6 Sep 2021 14:40:00 +1000 Subject: [PATCH] feat: add initial support for mutiple deployment targets --- go.mod | 2 + .../03-populate-api-data-kubernetes.gql | 6 +- node-packages/commons/src/api.ts | 75 ++- node-packages/commons/src/deploy-tasks.ts | 519 ++++++++++++++++++ node-packages/commons/src/tasks.ts | 425 +++++--------- .../docker-entrypoint-initdb.d/00-tables.sql | 46 +- .../01-migrations.sql | 38 ++ .../03-procedures.sql | 24 +- services/api/src/resolvers.js | 31 ++ .../resources/deploytargetconfig/helpers.ts | 29 + .../resources/deploytargetconfig/resolvers.ts | 255 +++++++++ .../src/resources/deploytargetconfig/sql.ts | 50 ++ .../src/resources/environment/resolvers.ts | 33 +- .../api/src/resources/openshift/resolvers.ts | 44 ++ .../api/src/resources/project/resolvers.ts | 32 ++ services/api/src/typeDefs.js | 68 ++- tests/tests/vars/test_vars.yaml | 18 +- 17 files changed, 1348 insertions(+), 347 deletions(-) create mode 100644 node-packages/commons/src/deploy-tasks.ts create mode 100644 services/api/src/resources/deploytargetconfig/helpers.ts create mode 100644 services/api/src/resources/deploytargetconfig/resolvers.ts create mode 100644 services/api/src/resources/deploytargetconfig/sql.ts diff --git a/go.mod b/go.mod index 5634c43235..af34e0f6ce 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/amazeeio/lagoon + +go 1.14 diff --git a/local-dev/api-data-watcher-pusher/api-data/03-populate-api-data-kubernetes.gql b/local-dev/api-data-watcher-pusher/api-data/03-populate-api-data-kubernetes.gql index f85ee0ed49..50a3f7a955 100644 --- a/local-dev/api-data-watcher-pusher/api-data/03-populate-api-data-kubernetes.gql +++ b/local-dev/api-data-watcher-pusher/api-data/03-populate-api-data-kubernetes.gql @@ -5,11 +5,11 @@ mutation PopulateApi { input: { id: 2001 name: "ci-local-control-k8s" - consoleUrl: "${CONSOLE_URL}" # make-host-docker-internal - routerPattern: "${project}.${environment}.${INGRESS_IP}.nip.io" + consoleUrl: "https://172.17.0.1:16643/" # make-host-docker-internal + routerPattern: "${project}.${environment}.172.17.0.1.nip.io" sshHost: "172.17.0.1" sshPort: "2020" - token: "${TOKEN}" # make-kubernetes-token + token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZWamZLTzEzZ2lPSGFtc0d6QXVkWXpDYi1fcmlfLWVBd3JtbEEydGItTHcifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJsYWdvb24iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoia3ViZXJuZXRlc2J1aWxkZGVwbG95LXRva2VuLXJxNDg1Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6Imt1YmVybmV0ZXNidWlsZGRlcGxveSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjA3YzViODAxLTI5ZDgtNDU5Ni1hODBlLTZlMmU3MmY3YmMwMCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpsYWdvb246a3ViZXJuZXRlc2J1aWxkZGVwbG95In0.srj-zZguNXCQbeTIS5GtJw7Jl61k_miC8hXED70NQULm6OAMkImHrURRCfD4kjKPy-jbwEI88m5TNLFP8_0sMfdwj2vr2Gv8fTC55qoAJ589ff_dwv8THSKdKNj6VaHynzEzQ4IfZscd3ogP4HYF9alt-X4mMcJ2BApBt4F13Hg-bE2-4uzO0b_u13pJhzn0XrH8JGXWP0_oMPtE7M0zJL9BfOrBph_MgSb2djSbVBNbhPJ0fs9-eIB5aAu0NmqPhpxj6WL4UOAKX178IsDAq4vtRZrScZwvZxRcaDUxZ-MgwewWI8Ll0yg7UCxtZTdkLglkCgpjTK33Ei0PXWdE4A" # make-kubernetes-token } ) { id diff --git a/node-packages/commons/src/api.ts b/node-packages/commons/src/api.ts index b9cdbd224b..f271fb12ac 100644 --- a/node-packages/commons/src/api.ts +++ b/node-packages/commons/src/api.ts @@ -807,6 +807,7 @@ export async function getEmailInfoForProject( } interface GetActiveSystemForProjectResult { + id: number; branches: string; pullrequests: string; activeSystemsDeploy: string; @@ -824,6 +825,7 @@ export async function getActiveSystemForProject( const result = await graphqlapi.query(` { project:projectByName(name: "${project}"){ + id activeSystemsDeploy activeSystemsPromote activeSystemsRemove @@ -975,21 +977,25 @@ export const addOrUpdateEnvironment = ( deployBaseRef: string, environmentType: string, openshiftProjectName: string, + openshift: number, + openshiftProjectPattern: string, deployHeadRef: string = null, deployTitle: string = null ): Promise => graphqlapi.mutate( ` -($name: String!, $project: Int!, $deployType: DeployType!, $deployBaseRef: String!, $deployHeadRef: String, $deployTitle: String, $environmentType: EnvType!, $openshiftProjectName: String!) { +($name: String!, $project: Int!, $openshift: Int, $openshiftProjectPattern: String, $deployType: DeployType!, $deployBaseRef: String!, $deployHeadRef: String, $deployTitle: String, $environmentType: EnvType!, $openshiftProjectName: String!) { addOrUpdateEnvironment(input: { name: $name, project: $project, + openshift: $openshift, deployType: $deployType, deployBaseRef: $deployBaseRef, deployHeadRef: $deployHeadRef, deployTitle: $deployTitle, environmentType: $environmentType, openshiftProjectName: $openshiftProjectName + openshiftProjectPattern: $openshiftProjectPattern }) { id name @@ -999,6 +1005,7 @@ export const addOrUpdateEnvironment = ( deployType environmentType openshiftProjectName + openshiftProjectPattern envVariables { name value @@ -1015,7 +1022,9 @@ export const addOrUpdateEnvironment = ( deployHeadRef, deployTitle, environmentType, - openshiftProjectName + openshiftProjectName, + openshift, + openshiftProjectPattern } ); @@ -1064,6 +1073,7 @@ export const getOpenShiftInfoForProject = (project: string): Promise => project:projectByName(name: "${project}"){ id openshift { + id name consoleUrl token @@ -1071,6 +1081,8 @@ export const getOpenShiftInfoForProject = (project: string): Promise => routerPattern monitoringConfig } + branches + pullrequests availability gitUrl privateKey @@ -1092,6 +1104,49 @@ export const getOpenShiftInfoForProject = (project: string): Promise => } `); +export const getDeployTargetConfigsForProject = (project: number): Promise => + graphqlapi.query(` + { + targets:deployTargetConfigsByProjectId(project: ${project}){ + id + weight + branches + pullrequests + weight + deployTargetProjectPattern + deployTarget{ + id + name + consoleUrl + token + projectUser + routerPattern + monitoringConfig + } + } + } +`); + +export const getOpenShiftInfoForEnvironment = (environment: number): Promise => + graphqlapi.query(` + { + environment:environmentById(id: ${environment}){ + id + name + openshiftProjectPattern + openshift { + id + name + consoleUrl + token + projectUser + routerPattern + monitoringConfig + } + } + } +`); + export const getBillingGroupForProject = (project: string): Promise => graphqlapi.query(` { @@ -1108,10 +1163,14 @@ export const getBillingGroupForProject = (project: string): Promise => interface GetEnvironentsForProjectEnvironmentResult { name: string; + id: number; environmentType: EnvType; + openshiftProjectPattern: string; + openshift: any; } interface GetEnvironentsForProjectProjectResult { + id: number; developmentEnvironmentsLimit: number; productionEnvironment: string; standbyProductionEnvironment: string; @@ -1128,10 +1187,20 @@ export const getEnvironmentsForProject = ( graphqlapi.query(` { project:projectByName(name: "${project}"){ + id developmentEnvironmentsLimit productionEnvironment standbyProductionEnvironment - environments(includeDeleted:false) { name, environmentType } + environments(includeDeleted:false) { + name + id + environmentType + openshiftProjectPattern + openshift{ + id + name + } + } } } `); diff --git a/node-packages/commons/src/deploy-tasks.ts b/node-packages/commons/src/deploy-tasks.ts new file mode 100644 index 0000000000..7a418203fc --- /dev/null +++ b/node-packages/commons/src/deploy-tasks.ts @@ -0,0 +1,519 @@ +import { logger } from './local-logging'; +import { + getOpenShiftInfoForProject, + getOpenShiftInfoForEnvironment, + getDeployTargetConfigsForProject, + getEnvironmentByName, + getEnvironmentsForProject +} from './api'; +import { + getControllerBuildData, + sendToLagoonTasks +} from './tasks'; + +class NoNeedToDeployBranch extends Error { + constructor(message) { + super(message); + this.name = 'NoNeedToDeployBranch'; + } +} + +class UnknownActiveSystem extends Error { + constructor(message) { + super(message); + this.name = 'UnknownActiveSystem'; + } +} + +/* + this function handles deploying a branch +*/ +const deployBranch = async function(data: any) { + const { + projectId, + projectName, + branchName, + project, + deployData, + deployTarget, + } = data; + + let branchesRegex = deployTarget.branches + switch (branchesRegex) { + case undefined: + case null: + logger.debug( + `projectName: ${projectName}, branchName: ${branchName}, no branches defined in active system, assuming we want all of them` + ); + switch (project.activeSystemsDeploy) { + case 'lagoon_controllerBuildDeploy': + deployData.deployTarget = deployTarget + const buildDeployData = await getControllerBuildData(deployData); + const sendTasks = await sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + return true + default: + throw new UnknownActiveSystem( + `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` + ); + } + case 'true': + logger.debug( + `projectName: ${projectName}, branchName: ${branchName}, all branches active, therefore deploying` + ); + switch (project.activeSystemsDeploy) { + case 'lagoon_controllerBuildDeploy': + deployData.deployTarget = deployTarget + const buildDeployData = await getControllerBuildData(deployData); + const sendTasks = await sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + return true + default: + throw new UnknownActiveSystem( + `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` + ); + } + case 'false': + logger.debug( + `projectName: ${projectName}, branchName: ${branchName}, branch deployments disabled` + ); + return false + default: { + logger.debug( + `projectName: ${projectName}, branchName: ${branchName}, regex ${branchesRegex}, testing if it matches` + ); + const branchRegex = new RegExp(branchesRegex); + if (branchRegex.test(branchName)) { + logger.debug( + `projectName: ${projectName}, branchName: ${branchName}, regex ${branchesRegex} matched branchname, starting deploy` + ); + switch (project.activeSystemsDeploy) { + case 'lagoon_controllerBuildDeploy': + // controllers uses a different message than the other services, so we need to source it here + deployData.deployTarget = deployTarget + const buildDeployData = await getControllerBuildData(deployData); + const sendTasks = await sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + return true + default: + throw new UnknownActiveSystem( + `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` + ); + } + } + logger.debug( + `projectName: ${projectName}, branchName: ${branchName}, regex ${branchesRegex} did not match branchname, not deploying` + ); + return false + } + } +} + +/* +this function handles deploying a pullrequest +*/ +const deployPullrquest = async function(data: any) { + const { + projectId, + projectName, + branchName, + project, + deployData, + pullrequestTitle, + deployTarget, + } = data; + + let pullrequestRegex = deployTarget.pullrequests + switch (pullrequestRegex) { + case undefined: + case null: + logger.debug( + `projectName: ${projectName}, pullrequest: ${branchName}, no pullrequest defined in active system, assuming we want all of them` + ); + switch (project.activeSystemsDeploy) { + case 'lagoon_controllerBuildDeploy': + deployData.deployTarget = deployTarget + const buildDeployData = await getControllerBuildData(deployData); + const sendTasks = await sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + return true + default: + throw new UnknownActiveSystem( + `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` + ); + } + case 'true': + logger.debug( + `projectName: ${projectName}, pullrequest: ${branchName}, all pullrequest active, therefore deploying` + ); + switch (project.activeSystemsDeploy) { + case 'lagoon_controllerBuildDeploy': + deployData.deployTarget = deployTarget + const buildDeployData = await getControllerBuildData(deployData); + const sendTasks = await sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + return true + default: + throw new UnknownActiveSystem( + `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` + ); + } + case 'false': + logger.debug( + `projectName: ${projectName}, pullrequest: ${branchName}, pullrequest deployments disabled` + ); + return false + default: { + logger.debug( + `projectName: ${projectName}, pullrequest: ${branchName}, regex ${pullrequestRegex}, testing if it matches PR title '${pullrequestTitle}'` + ); + const branchRegex = new RegExp(pullrequestRegex); + if (branchRegex.test(pullrequestTitle)) { + logger.debug( + `projectName: ${projectName}, pullrequest: ${branchName}, regex ${pullrequestRegex} matched PR title '${pullrequestTitle}', starting deploy` + ); + switch (project.activeSystemsDeploy) { + case 'lagoon_controllerBuildDeploy': + // controllers uses a different message than the other services, so we need to source it here + deployData.deployTarget = deployTarget + const buildDeployData = await getControllerBuildData(deployData); + const sendTasks = await sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + return true + default: + throw new UnknownActiveSystem( + `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` + ); + } + } + logger.debug( + `projectName: ${projectName}, branchName: ${branchName}, regex ${pullrequestRegex} did not match PR title, not deploying` + ); + return false + } + } +} + +/* +this is the primary function that handles checking the existing `openshift` configured for a deployed branch +it will check if the environment is already deployed, and if so will consume the openshift that it contains +otherwise it will check if there are deploytargetconfigs defined and use those (and only those) +if there are no deploytargetconfigs defined, then it will use what is defined in the project +*/ +export const deployTargetBranches = async function(data: any) { + const { + projectId, + projectName, + branchName, + project, + deployData + } = data; + + let deployTarget + + // see if the environment has already been created/deployed and get the openshift and projectpattern out of it + try { + const apiEnvironment = await getEnvironmentByName(branchName, projectId); + let envId = apiEnvironment.environmentByName.id + const environmentOpenshift = await getOpenShiftInfoForEnvironment(envId); + deployTarget = { + openshiftProjectPattern: environmentOpenshift.environment.openshiftProjectPattern, + branches: branchName, + openshift: environmentOpenshift.environment.openshift + } + } catch (err) { + //do nothing if there is an error, likely means that the environment hasn't been deployed before + } + // check if this is an active/standby deployment + const activeStandby = await checkActiveStandbyDeployTarget(data) + if (deployTarget && activeStandby) { + if (deployTarget.openshift.id === activeStandby.environment.openshift.id) { + // if the deployed environment matches the opposite active/standby environment target + // then we allow the deployment to continue + logger.debug(`TODO: THEY MATCH ${deployTarget.openshift.id} - ${activeStandby.environment.openshift.id}`) + } else { + // but if the deployed environment is on a different target + // we cannot allow the deployment to proceed as active/standby is not cross cluster compatable at the moment + throw new NoNeedToDeployBranch( + `TODO: THEY DON'T MATCH, NOT DEPLOYING` + ); + } + } + + // if there is an openshift attached to the environment, then deploy deploy the environment using this deploytarget + if (deployTarget) { + data.deployTarget = deployTarget + let deploy = await deployBranch(data) + logger.info(`TODO: EXISTING DEPLOY VIA ENVIRONMENT OPENSHIFT`) + return deploy + } + + // otherwise this is probably the first time the environment is being deployed + // check if there are any deploytarget configs defined for this project + const deployTargetConfigs = await getDeployTargetConfigsForProject(projectId) + let deploy = false + if (deployTargetConfigs.targets.length > 0) { + // if there are any deploytarget configs, check through them + for (let i = 0; i < deployTargetConfigs.targets.length; i++) { + deployTarget = { + openshiftProjectPattern: deployTargetConfigs.targets[i].deployTargetProjectPattern, + branches: deployTargetConfigs.targets[i].branches, + // since deploytarget configs reference a deploytarget instead of an openshift, convert that here to be what it needs to be + openshift: deployTargetConfigs.targets[i].deployTarget + } + data.deployTarget = deployTarget + logger.info(`TODO: NEW DEPLOY VIA DEPLOYTARGETCONFIG OPENSHIFT ${JSON.stringify(deployTarget)}`) + deploy = await deployBranch(data) + if (deploy) { + // if the deploy is successful, then return + return deploy + } + } + if (deploy == false) { + throw new NoNeedToDeployBranch( + `configured regex for all deploytargets does not match branchname '${branchName}'` + ); + } + } else { + // deploy the project using the projects default target + let deployTarget + try { + const projectOpenshift = await getOpenShiftInfoForProject(projectName); + logger.info(`TODO: PROJECT ${projectId} ${JSON.stringify(projectOpenshift)}`) + deployTarget = { + openshiftProjectPattern: projectOpenshift.project.openshiftProjectPattern, + branches: projectOpenshift.project.branches, + openshift: projectOpenshift.project.openshift + } + } catch (err) { + //do nothing if there is an error, likely means that the environment hasn't been deployed before + } + data.deployTarget = deployTarget + let deploy = await deployBranch(data) + logger.info(`TODO: NEW DEPLOY VIA PROJECT OPENSHIFT`) + return deploy + } + throw new NoNeedToDeployBranch( + `no deploy targets configured` + ); +} + +/* +this is the primary function that handles checking the existing `openshift` configured for a deployed branch +it will check if the environment is already deployed, and if so will consume the openshift that it contains +otherwise it will check if there are deploytargetconfigs defined and use those (and only those) +if there are no deploytargetconfigs defined, then it will use what is defined in the project +*/ +export const deployTargetPullrequest = async function(data: any) { + const { + projectId, + projectName, + branchName, + project, + deployData + } = data; + + let deployTarget + // see if the environment has already been created/deployed and get the openshift and projectpattern out of it + try { + const apiEnvironment = await getEnvironmentByName(branchName, projectId); + let envId = apiEnvironment.environmentByName.id + const environmentOpenshift = await getOpenShiftInfoForEnvironment(envId); + deployTarget = { + openshiftProjectPattern: environmentOpenshift.environment.openshiftProjectPattern, + pullrequests: branchName, + openshift: environmentOpenshift.environment.openshift + } + } catch (err) { + //do nothing if there is an error, likely means that the environment hasn't been deployed before + } + // if there is an openshift attached to the environment, then deploy deploy the environment using this deploytarget + if (deployTarget) { + data.deployTarget = deployTarget + let deploy = await deployPullrquest(data) + logger.info(`TODO: EXISTING DEPLOY VIA ENVIRONMENT OPENSHIFT`) + return deploy + } + + // otherwise this is probably the first time the environment is being deployed + // check if there are any deploytarget configs defined for this project + const deployTargetConfigs = await getDeployTargetConfigsForProject(projectId) + let deploy = false + if (deployTargetConfigs.targets.length > 0) { + // if there are any deploytarget configs, check through them + for (let i = 0; i < deployTargetConfigs.targets.length; i++) { + deployTarget = { + openshiftProjectPattern: deployTargetConfigs.targets[i].deployTargetProjectPattern, + branches: deployTargetConfigs.targets[i].branches, + // since deploytarget configs reference a deploytarget instead of an openshift, convert that here to be what it needs to be + openshift: deployTargetConfigs.targets[i].deployTarget + } + data.deployTarget = deployTarget + logger.info(`TODO: NEW DEPLOY VIA DEPLOYTARGETCONFIG OPENSHIFT ${JSON.stringify(deployTarget)}`) + deploy = await deployPullrquest(data) + if (deploy) { + // if the deploy is successful, then return + return deploy + } + } + if (deploy == false) { + throw new NoNeedToDeployBranch( + `configured regex for all deploytargets does not match pullrequest '${branchName}'` + ); + } + } else { + // deploy the project using the projects default target + let deployTarget + try { + const projectOpenshift = await getOpenShiftInfoForProject(projectName); + deployTarget = { + openshiftProjectPattern: projectOpenshift.project.openshiftProjectPattern, + branches: projectOpenshift.project.branches, + openshift: projectOpenshift.project.openshift + } + } catch (err) { + //do nothing if there is an error, likely means that the environment hasn't been deployed before + } + data.deployTarget = deployTarget + let deploy = await deployPullrquest(data) + logger.info(`TODO: NEW DEPLOY VIA PROJECT OPENSHIFT`) + return deploy + } + throw new NoNeedToDeployBranch( + `no deploy targets configured` + ); +} + +/* +this is the primary function that handles checking the existing `openshift` configured for a deployed promote +*/ +export const deployTargetPromote = async function(data: any) { + const { + projectId, + promoteData + } = data; + + let deployTarget + const projectOpenshift = await getOpenShiftInfoForProject(promoteData.projectName) + deployTarget = { + openshiftProjectPattern: projectOpenshift.project.openshiftProjectPattern, + branches: projectOpenshift.project.branches, + openshift: projectOpenshift.project.openshift + } + const deployTargetConfigs = await getDeployTargetConfigsForProject(projectId) + if (deployTargetConfigs.targets.length > 0) { + const promoteSourceEnvOpenshift = await checkPromoteEnvironment(promoteData) + if (promoteSourceEnvOpenshift) { + deployTarget = { + openshiftProjectPattern: promoteSourceEnvOpenshift.environment.openshiftProjectPattern, + branches: promoteSourceEnvOpenshift.environment.branches, + openshift: promoteSourceEnvOpenshift.environment.openshift + } + } else { + throw new NoNeedToDeployBranch( + `TODO: NO EXISTING ENVIRONMENT TO PROMOTE FROM WITH A VALID TARGET` + ); + } + } + promoteData.deployTarget = deployTarget + const buildDeployData = await getControllerBuildData(promoteData); + const sendTasks = await sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + return true +} + +/* +this is the primary function that handles checking the existing `openshift` configured for an active/standby deployed environment +the main features are to check if a production or standby production environment are already deployed, and to return the data for those particular +environments +this information is used in the `deployTargetBranches` function to return an error to the user if they attempt to deploy either the production or standbyproduction +environment to different clusters than each other +*/ +export const checkActiveStandbyDeployTarget = async function(data: any) { + const { + projectId, + projectName, + branchName, + project, + deployData + } = data; + let result + const environments = await getEnvironmentsForProject(projectName); + logger.info(`FOUND ${environments.project.standbyProductionEnvironment} ${branchName}`) + if (environments.project.standbyProductionEnvironment === branchName) { + // this is the standby environment being deployed + // Check to ensure the environment actually exists. + let environmentId = 0; + let foundEnvironment = false; + environments.project.environments.forEach(function( + environment, + index + ) { + // check that the production environment exists + if (environment.name === environments.project.productionEnvironment) { + foundEnvironment = true; + environmentId = environment.id; + } + }); + + if (foundEnvironment) { + logger.info(`FOUND ${environmentId}`) + // if the production environment exists, then check which openshift it has been deployed to + result = await getOpenShiftInfoForEnvironment(environmentId); + } + } + if (environments.project.productionEnvironment === branchName) { + // this is the standby environment being deployed + // Check to ensure the environment actually exists. + let environmentId = 0; + let foundEnvironment = false; + environments.project.environments.forEach(function( + environment, + index + ) { + // check that the production environment exists + if (environment.name === environments.project.standbyProductionEnvironment) { + foundEnvironment = true; + environmentId = environment.id; + } + }); + + if (foundEnvironment) { + logger.info(`FOUND ${environmentId}`) + // if the production environment exists, then check which openshift it has been deployed to + result = await getOpenShiftInfoForEnvironment(environmentId); + } + } + return result +} + +/* +this is the primary function that handles checking the existing `openshift` configured for a promoted environment +currently promoted environments can only be promoted on the same cluster as the environment it is being promoted from +*/ +export const checkPromoteEnvironment = async function(data: any) { + const { + projectId, + projectName, + branchName, + project, + promoteSourceEnvironment, + deployData + } = data; + let result + const environments = await getEnvironmentsForProject(projectName); + logger.info(`PROMOTE SOURCE ${promoteSourceEnvironment}`) + // check the sourceenvironment exists and get the openshift info for it + let environmentId = 0; + let foundEnvironment = false; + environments.project.environments.forEach(function( + environment, + index + ) { + // check that the production environment exists + if (environment.name === promoteSourceEnvironment) { + foundEnvironment = true; + environmentId = environment.id; + } + }); + + if (foundEnvironment) { + logger.info(`FOUND ${environmentId}`) + // if the production environment exists, then check which openshift it has been deployed to + result = await getOpenShiftInfoForEnvironment(environmentId); + } + return result +} \ No newline at end of file diff --git a/node-packages/commons/src/tasks.ts b/node-packages/commons/src/tasks.ts index ead5c8c32d..927dd90339 100644 --- a/node-packages/commons/src/tasks.ts +++ b/node-packages/commons/src/tasks.ts @@ -10,11 +10,18 @@ import { getActiveSystemForProject, getEnvironmentsForProject, getOpenShiftInfoForProject, + getOpenShiftInfoForEnvironment, + getDeployTargetConfigsForProject, getBillingGroupForProject, addOrUpdateEnvironment, getEnvironmentByName, addDeployment } from './api'; +import { + deployTargetBranches, + deployTargetPullrequest, + deployTargetPromote +} from './deploy-tasks'; import sha1 from 'sha1'; import crypto from 'crypto'; import moment from 'moment'; @@ -112,10 +119,10 @@ class UnknownActiveSystem extends Error { } } -class NoNeedToDeployBranch extends Error { +class CannotDeployWithDeployTargetConfigs extends Error { constructor(message) { super(message); - this.name = 'NoNeedToDeployBranch'; + this.name = 'CannotDeployWithDeployTargetConfigs'; } } @@ -254,7 +261,7 @@ export const createTaskMonitor = async function(task: string, payload: any) { const makeSafe = string => string.toLocaleLowerCase().replace(/[^0-9a-z-]/g,'-') // @TODO: make sure if it fails, it does so properly -const getControllerBuildData = async function(deployData: any) { +export const getControllerBuildData = async function(deployData: any) { const { projectName, branchName, @@ -265,16 +272,14 @@ const getControllerBuildData = async function(deployData: any) { headSha, baseBranchName: baseBranch, baseSha, - promoteSourceEnvironment + promoteSourceEnvironment, + deployTarget } = deployData; - const project = await getActiveSystemForProject(projectName, 'Deploy'); - // const environments = await getEnvironmentsForProject(projectName); - var environmentName = makeSafe(branchName) const result = await getOpenShiftInfoForProject(projectName); - const projectOpenShift = result.project + const lagoonProjectData = result.project const billingGroupResult = await getBillingGroupForProject(projectName); const projectBillingGroup = billingGroupResult.project @@ -287,20 +292,18 @@ const getControllerBuildData = async function(deployData: any) { var environmentType = 'development' if ( - projectOpenShift.productionEnvironment === environmentName - || projectOpenShift.standbyProductionEnvironment === environmentName + lagoonProjectData.productionEnvironment === environmentName + || lagoonProjectData.standbyProductionEnvironment === environmentName ) { environmentType = 'production' } var gitSha = sha as string - var projectTargetName = projectOpenShift.openshift.name - var openshiftProject = projectOpenShift.openshiftProjectPattern ? projectOpenShift.openshiftProjectPattern.replace('${environment}',environmentName).replace('${project}', projectName) : `${projectName}-${environmentName}` - var deployPrivateKey = projectOpenShift.privateKey - var gitUrl = projectOpenShift.gitUrl - var projectProductionEnvironment = projectOpenShift.productionEnvironment - var projectStandbyEnvironment = projectOpenShift.standbyProductionEnvironment - var subfolder = projectOpenShift.subfolder || "" - var routerPattern = projectOpenShift.routerPattern || projectOpenShift.openshift.routerPattern + + var deployPrivateKey = lagoonProjectData.privateKey + var gitUrl = lagoonProjectData.gitUrl + var projectProductionEnvironment = lagoonProjectData.productionEnvironment + var projectStandbyEnvironment = lagoonProjectData.standbyProductionEnvironment + var subfolder = lagoonProjectData.subfolder || "" var prHeadBranch = headBranch || "" var prHeadSha = headSha || "" var prBaseBranch = baseBranch || "" @@ -315,21 +318,6 @@ const getControllerBuildData = async function(deployData: any) { var alertContactHA = "" var alertContactSA = "" var uptimeRobotStatusPageIds = [] - var monitoringConfig: any = {}; - try { - monitoringConfig = JSON.parse(projectOpenShift.openshift.monitoringConfig) || "invalid" - } catch (e) { - logger.error('Error parsing openshift.monitoringConfig from openshift: %s, continuing with "invalid"', projectOpenShift.openshift.name, { error: e }) - monitoringConfig = "invalid" - } - if (monitoringConfig != "invalid"){ - alertContactHA = monitoringConfig.uptimerobot.alertContactHA || "" - alertContactSA = monitoringConfig.uptimerobot.alertContactSA || "" - if (monitoringConfig.uptimerobot.statusPageId) { - uptimeRobotStatusPageIds.push(monitoringConfig.uptimerobot.statusPageId) - } - } - var availability = projectOpenShift.availability || "STANDARD" var alertContact = "" if (alertContactHA != undefined && alertContactSA != undefined){ @@ -391,11 +379,60 @@ const getControllerBuildData = async function(deployData: any) { break; } + // Get the target information + // get the projectpattern and id from the target + // this is only used on the initial deployment + + var openshiftProjectPattern = deployTarget.openshiftProjectPattern; + // check if this environment already exists in the API so we can get the openshift target it is using + // this is even valid for promotes if it isn't the first time time it is being deployed + try { + const apiEnvironment = await getEnvironmentByName(branchName, lagoonProjectData.id); + let envId = apiEnvironment.environmentByName.id + const environmentOpenshift = await getOpenShiftInfoForEnvironment(envId); + deployTarget.openshift = environmentOpenshift.environment.openshift + openshiftProjectPattern = environmentOpenshift.environment.openshiftProjectPattern + } catch (err) { + //do nothing + } + // end working out the target information + let openshiftId = deployTarget.openshift.id; + + var openshiftProject = openshiftProjectPattern ? openshiftProjectPattern.replace('${environment}',environmentName).replace('${project}', projectName) : `${projectName}-${environmentName}` + + var routerPattern = lagoonProjectData.routerPattern || deployTarget.routerPattern + var deployTargetName = deployTarget.openshift.name + var monitoringConfig: any = {}; + try { + monitoringConfig = JSON.parse(deployTarget.openshift.monitoringConfig) || "invalid" + } catch (e) { + logger.error('Error parsing openshift.monitoringConfig from openshift: %s, continuing with "invalid"', deployTarget.openshift.name, { error: e }) + monitoringConfig = "invalid" + } + if (monitoringConfig != "invalid"){ + alertContactHA = monitoringConfig.uptimerobot.alertContactHA || "" + alertContactSA = monitoringConfig.uptimerobot.alertContactSA || "" + if (monitoringConfig.uptimerobot.statusPageId) { + uptimeRobotStatusPageIds.push(monitoringConfig.uptimerobot.statusPageId) + } + } + + var availability = lagoonProjectData.availability || "STANDARD" + // @TODO: openshiftProject here can't be generated on the cluster side (it should be) but the addOrUpdate mutation doesn't allow for openshiftProject to be optional // maybe need to have this generate a random uid initially? let environment; try { - environment = await addOrUpdateEnvironment(branchName, projectOpenShift.id, graphqlGitType, deployBaseRef, graphqlEnvironmentType, openshiftProject, deployHeadRef, deployTitle) + environment = await addOrUpdateEnvironment(branchName, + lagoonProjectData.id, + graphqlGitType, + deployBaseRef, + graphqlEnvironmentType, + openshiftProject, + openshiftId, + openshiftProjectPattern, + deployHeadRef, + deployTitle) logger.info(`${openshiftProject}: Created/Updated Environment in API`) } catch (err) { logger.error(err) @@ -409,17 +446,17 @@ const getControllerBuildData = async function(deployData: any) { let environmentId; try { const now = moment.utc(); - const apiEnvironment = await getEnvironmentByName(branchName, projectOpenShift.id); + const apiEnvironment = await getEnvironmentByName(branchName, lagoonProjectData.id); environmentId = apiEnvironment.environmentByName.id deployment = await addDeployment(buildName, "NEW", now.format('YYYY-MM-DDTHH:mm:ss'), apiEnvironment.environmentByName.id); } catch (error) { - logger.error(`Could not save deployment for project ${projectOpenShift.id}. Message: ${error}`); + logger.error(`Could not save deployment for project ${lagoonProjectData.id}. Message: ${error}`); } // encode some values so they get sent to the controllers nicely const sshKeyBase64 = new Buffer(deployPrivateKey.replace(/\\n/g, "\n")).toString('base64') const envVars = new Buffer(JSON.stringify(environment.addOrUpdateEnvironment.envVariables)).toString('base64') - const projectVars = new Buffer(JSON.stringify(projectOpenShift.envVariables)).toString('base64') + const projectVars = new Buffer(JSON.stringify(lagoonProjectData.envVariables)).toString('base64') // this is what will be returned and sent to the controllers via message queue, it is the lagoonbuild controller spec var buildDeployData: any = { @@ -440,7 +477,7 @@ const getControllerBuildData = async function(deployData: any) { ...promoteData, gitReference: gitRef, project: { - id: projectOpenShift.id, + id: lagoonProjectData.id, name: projectName, gitUrl: gitUrl, uiLink: deployment.addDeployment.uiLink, @@ -451,7 +488,7 @@ const getControllerBuildData = async function(deployData: any) { standbyEnvironment: projectStandbyEnvironment, subfolder: subfolder, routerPattern: routerPattern, - deployTarget: projectTargetName, + deployTarget: deployTargetName, projectSecret: projectSecret, key: sshKeyBase64, registry: registry, @@ -469,6 +506,11 @@ const getControllerBuildData = async function(deployData: any) { return buildDeployData; } +/* + This `createDeployTask` is the primary entrypoint after the + API resolvers to handling a deployment creation + and the associated environment creation. +*/ export const createDeployTask = async function(deployData: any) { const { projectName, @@ -551,140 +593,26 @@ export const createDeployTask = async function(deployData: any) { } if (type === 'branch') { - switch (project.branches) { - case undefined: - case null: - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}, no branches defined in active system, assuming we want all of them` - ); - switch (project.activeSystemsDeploy) { - case 'lagoon_controllerBuildDeploy': - // controllers uses a different message than the other services, so we need to source it here - const buildDeployData = await getControllerBuildData(deployData); - return sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); - default: - throw new UnknownActiveSystem( - `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` - ); - } - case 'true': - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}, all branches active, therefore deploying` - ); - switch (project.activeSystemsDeploy) { - case 'lagoon_controllerBuildDeploy': - // controllers uses a different message than the other services, so we need to source it here - const buildDeployData = await getControllerBuildData(deployData); - return sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); - default: - throw new UnknownActiveSystem( - `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` - ); - } - case 'false': - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}, branch deployments disabled` - ); - throw new NoNeedToDeployBranch('Branch deployments disabled'); - default: { - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}, regex ${project.branches}, testing if it matches` - ); - const branchRegex = new RegExp(project.branches); - if (branchRegex.test(branchName)) { - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}, regex ${project.branches} matched branchname, starting deploy` - ); - switch (project.activeSystemsDeploy) { - case 'lagoon_controllerBuildDeploy': - // controllers uses a different message than the other services, so we need to source it here - const buildDeployData = await getControllerBuildData(deployData); - return sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); - default: - throw new UnknownActiveSystem( - `Unknown active system '${project.activeSystemsDeploy}' for task 'deploy' in for project ${projectName}` - ); - } - } - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}, regex ${project.branches} did not match branchname, not deploying` - ); - throw new NoNeedToDeployBranch( - `configured regex '${project.branches}' does not match branchname '${branchName}'` - ); - } + // use deployTargetBranches function to handle + let lagoonData = { + projectId: environments.project.id, + projectName, + branchName, + project, + deployData } + return deployTargetBranches(lagoonData) } else if (type === 'pullrequest') { - switch (project.pullrequests) { - case undefined: - case null: - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}, no pullrequest defined in active system, assuming we want all of them` - ); - switch (project.activeSystemsDeploy) { - case 'lagoon_controllerBuildDeploy': - // controllers uses a different message than the other services, so we need to source it here - const buildDeployData = await getControllerBuildData(deployData); - return sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); - default: - throw new UnknownActiveSystem( - `Unknown active system '${ - project.activeSystemsDeploy - }' for task 'deploy' in for project ${projectName}`, - ); - } - case 'true': - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}, all pullrequest active, therefore deploying` - ); - switch (project.activeSystemsDeploy) { - case 'lagoon_controllerBuildDeploy': - // controllers uses a different message than the other services, so we need to source it here - const buildDeployData = await getControllerBuildData(deployData); - return sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); - default: - throw new UnknownActiveSystem( - `Unknown active system '${ - project.activeSystemsDeploy - }' for task 'deploy' in for project ${projectName}`, - ); - } - case 'false': - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}, pullrequest deployments disabled` - ); - throw new NoNeedToDeployBranch('PullRequest deployments disabled'); - default: { - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}, regex ${project.pullrequests}, testing if it matches PR Title '${pullrequestTitle}'` - ); - - const branchRegex = new RegExp(project.pullrequests); - if (branchRegex.test(pullrequestTitle)) { - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}, regex ${project.pullrequests} matched PR Title '${pullrequestTitle}', starting deploy` - ); - switch (project.activeSystemsDeploy) { - case 'lagoon_controllerBuildDeploy': - // controllers uses a different message than the other services, so we need to source it here - const buildDeployData = await getControllerBuildData(deployData); - return sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); - default: - throw new UnknownActiveSystem( - `Unknown active system '${ - project.activeSystemsDeploy - }' for task 'deploy' in for project ${projectName}`, - ); - } - } - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}, regex ${project.pullrequests} did not match PR Title, not deploying` - ); - throw new NoNeedToDeployBranch( - `configured regex '${project.pullrequests}' does not match PR Title '${pullrequestTitle}'` - ); - } + // use deployTargetPullrequest function to handle + let lagoonData = { + projectId: environments.project.id, + projectName, + branchName, + project, + pullrequestTitle, + deployData } + return deployTargetPullrequest(lagoonData) } break; default: @@ -712,8 +640,12 @@ export const createPromoteTask = async function(promoteData: any) { switch (project.activeSystemsPromote) { case 'lagoon_controllerBuildDeploy': - const buildDeployData = await getControllerBuildData(promoteData); - return sendToLagoonTasks(buildDeployData.spec.project.deployTarget+':builddeploy', buildDeployData); + // use deployTargetPromote function to handle + let lagoonData = { + projectId: project.id, + promoteData + } + return deployTargetPromote(lagoonData) default: throw new UnknownActiveSystem( `Unknown active system '${project.activeSystemsPromote}' for task 'deploy' in for project ${projectName}` @@ -758,131 +690,12 @@ export const createRemoveTask = async function(removeData: any) { } switch (project.activeSystemsRemove) { - case 'lagoon_openshiftRemove': - if (type === 'branch') { - // Check to ensure the environment actually exists. - let foundEnvironment = false; - allEnvironments.project.environments.forEach(function( - environment, - index - ) { - if (environment.name === branch) { - foundEnvironment = true; - } - }); - - if (!foundEnvironment) { - logger.debug( - `projectName: ${projectName}, branchName: ${branch}, no environment found.` - ); - throw new NoNeedToRemoveBranch( - 'Branch environment does not exist, no need to remove anything.' - ); - } - - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}. Removing branch environment.` - ); - return sendToLagoonTasks('remove-openshift', removeData); - } else if (type === 'pullrequest') { - // Work out the branch name from the PR number. - let branchName = 'pr-' + pullrequestNumber; - removeData.branchName = 'pr-' + pullrequestNumber; - - // Check to ensure the environment actually exists. - let foundEnvironment = false; - allEnvironments.project.environments.forEach(function( - environment, - index - ) { - if (environment.name === branchName) { - foundEnvironment = true; - } - }); - - if (!foundEnvironment) { - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}, no pullrequest found.` - ); - throw new NoNeedToRemoveBranch( - 'Pull Request environment does not exist, no need to remove anything.' - ); - } - - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}. Removing pullrequest environment.` - ); - return sendToLagoonTasks('remove-openshift', removeData); - } else if (type === 'promote') { - return sendToLagoonTasks('remove-openshift', removeData); - } - break; - - case 'lagoon_kubernetesRemove': - if (type === 'branch') { - // Check to ensure the environment actually exists. - let foundEnvironment = false; - allEnvironments.project.environments.forEach(function( - environment, - index - ) { - if (environment.name === branch) { - foundEnvironment = true; - } - }); - - if (!foundEnvironment) { - logger.debug( - `projectName: ${projectName}, branchName: ${branch}, no environment found.` - ); - throw new NoNeedToRemoveBranch( - 'Branch environment does not exist, no need to remove anything.' - ); - } - - logger.debug( - `projectName: ${projectName}, branchName: ${branchName}. Removing branch environment.` - ); - return sendToLagoonTasks('remove-kubernetes', removeData); - } else if (type === 'pullrequest') { - // Work out the branch name from the PR number. - let branchName = 'pr-' + pullrequestNumber; - removeData.branchName = 'pr-' + pullrequestNumber; - - // Check to ensure the environment actually exists. - let foundEnvironment = false; - allEnvironments.project.environments.forEach(function( - environment, - index - ) { - if (environment.name === branchName) { - foundEnvironment = true; - } - }); - - if (!foundEnvironment) { - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}, no pullrequest found.` - ); - throw new NoNeedToRemoveBranch( - 'Pull Request environment does not exist, no need to remove anything.' - ); - } - - logger.debug( - `projectName: ${projectName}, pullrequest: ${branchName}. Removing pullrequest environment.` - ); - return sendToLagoonTasks('remove-kubernetes', removeData); - } else if (type === 'promote') { - return sendToLagoonTasks('remove-kubernetes', removeData); - } - break; - + // removed `openshift` and `kubernetes` remove functionality, these services no longer exist in Lagoon // handle removals using the controllers, send the message to our specific target cluster queue case 'lagoon_controllerRemove': - const result = await getOpenShiftInfoForProject(projectName); - const deployTarget = result.project.openshift.name + // @TODO: use `deployTargetConfigs` if (type === 'branch') { + let environmentId = 0; // Check to ensure the environment actually exists. let foundEnvironment = false; allEnvironments.project.environments.forEach(function( @@ -891,6 +704,7 @@ export const createRemoveTask = async function(removeData: any) { ) { if (environment.name === branch) { foundEnvironment = true; + environmentId = environment.id; } }); @@ -902,7 +716,8 @@ export const createRemoveTask = async function(removeData: any) { 'Branch environment does not exist, no need to remove anything.' ); } - + const result = await getOpenShiftInfoForEnvironment(environmentId); + const deployTarget = result.environment.openshift.name logger.debug( `projectName: ${projectName}, branchName: ${branchName}. Removing branch environment.` ); @@ -913,6 +728,7 @@ export const createRemoveTask = async function(removeData: any) { let branchName = 'pr-' + pullrequestNumber; removeData.branchName = 'pr-' + pullrequestNumber; + let environmentId = 0; // Check to ensure the environment actually exists. let foundEnvironment = false; allEnvironments.project.environments.forEach(function( @@ -921,6 +737,7 @@ export const createRemoveTask = async function(removeData: any) { ) { if (environment.name === branchName) { foundEnvironment = true; + environmentId = environment.id; } }); @@ -932,12 +749,16 @@ export const createRemoveTask = async function(removeData: any) { 'Pull Request environment does not exist, no need to remove anything.' ); } - + const result = await getOpenShiftInfoForEnvironment(environmentId); + const deployTarget = result.environment.openshift.name logger.debug( `projectName: ${projectName}, pullrequest: ${branchName}. Removing pullrequest environment.` ); return sendToLagoonTasks(deployTarget+":remove", removeData); } else if (type === 'promote') { + // promote cannot be used for deploytargetconfig configured environments + const result = await getOpenShiftInfoForProject(projectName); + const deployTarget = result.project.openshift.name return sendToLagoonTasks(deployTarget+":remove", removeData); } break; @@ -1013,9 +834,9 @@ export const createTaskTask = async function(taskData: any) { switch (projectSystem.activeSystemsTask) { case 'lagoon_controllerJob': // since controllers queues are named, we have to send it to the right tasks queue - // do that here - const result = await getOpenShiftInfoForProject(project.name); - const deployTarget = result.project.openshift.name + // do that here by querying which deploytarget the environment uses + const result = await getOpenShiftInfoForEnvironment(taskData.data.environment.id); + const deployTarget = result.environment.openshift.name return sendToLagoonTasks(deployTarget+":jobs", taskData); default: @@ -1041,9 +862,9 @@ export const createMiscTask = async function(taskData: any) { updatedKey = `kubernetes:${key}`; taskId = 'misc-kubernetes'; // determine the deploy target (openshift/kubernetes) for the task to go to - const result = await getOpenShiftInfoForProject(project.name); - const projectOpenShift = result.project - var deployTarget = projectOpenShift.openshift.name + // we get this from the environment + const result = await getOpenShiftInfoForEnvironment(taskData.data.environment.id); + const deployTarget = result.environment.openshift.name // this is the json structure for sending a misc task to the controller // there are some additional bits that can be adjusted, and these are done in the switch below on `updatedKey` var miscTaskData: any = { diff --git a/services/api-db/docker-entrypoint-initdb.d/00-tables.sql b/services/api-db/docker-entrypoint-initdb.d/00-tables.sql index 42616c5f0d..85fa48e445 100644 --- a/services/api-db/docker-entrypoint-initdb.d/00-tables.sql +++ b/services/api-db/docker-entrypoint-initdb.d/00-tables.sql @@ -117,25 +117,39 @@ CREATE TABLE IF NOT EXISTS billing_modifier ( ); CREATE TABLE IF NOT EXISTS environment ( - id int NOT NULL auto_increment PRIMARY KEY, - name varchar(100), - project int REFERENCES project (id), - deploy_type ENUM('branch', 'pullrequest', 'promote') NOT NULL, - deploy_base_ref varchar(100), - deploy_head_ref varchar(100), - deploy_title varchar(300), - environment_type ENUM('production', 'development') NOT NULL, - auto_idle int(1) NOT NULL default 1, - openshift_project_name varchar(100), - route varchar(300), - routes text, - monitoring_urls text, - updated timestamp DEFAULT CURRENT_TIMESTAMP, - created timestamp DEFAULT CURRENT_TIMESTAMP, - deleted timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + id int NOT NULL auto_increment PRIMARY KEY, + name varchar(100), + project int REFERENCES project (id), + deploy_type ENUM('branch', 'pullrequest', 'promote') NOT NULL, + deploy_base_ref varchar(100), + deploy_head_ref varchar(100), + deploy_title varchar(300), + environment_type ENUM('production', 'development') NOT NULL, + auto_idle int(1) NOT NULL default 1, + openshift_project_name varchar(100), + route varchar(300), + routes text, + monitoring_urls text, + openshift int REFERENCES openshift (id), + openshift_project_pattern varchar(300), + updated timestamp DEFAULT CURRENT_TIMESTAMP, + created timestamp DEFAULT CURRENT_TIMESTAMP, + deleted timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', UNIQUE KEY `project_name_deleted` (`project`,`name`, `deleted`) ); +-- add table for holding deploy_target configurations +-- these are used in replacement of the default project openshift target +CREATE TABLE IF NOT EXISTS deploy_target_config ( + id int NOT NULL auto_increment PRIMARY KEY, + project int REFERENCES project (id), + weight int NOT NULL DEFAULT 0, + branches varchar(300), + pullrequests varchar(300), + deploy_target int REFERENCES openshift (id), + deploy_target_project_pattern varchar(300) +); + CREATE TABLE IF NOT EXISTS environment_storage ( id int NOT NULL auto_increment PRIMARY KEY, environment int REFERENCES environment (id), diff --git a/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql b/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql index ee2da02717..ff9a34d23a 100644 --- a/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql +++ b/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql @@ -1410,6 +1410,42 @@ CREATE OR REPLACE PROCEDURE END; $$ +CREATE OR REPLACE PROCEDURE + add_openshift_to_environment() + + BEGIN + IF NOT EXISTS( + SELECT NULL + FROM INFORMATION_SCHEMA.COLUMNS + WHERE + table_name = 'environment' + AND table_schema = 'infrastructure' + AND column_name = 'openshift' + ) THEN + ALTER TABLE `environment` + ADD `openshift` int; + END IF; + END; +$$ + +CREATE OR REPLACE PROCEDURE + add_openshift_project_pattern_to_environment() + + BEGIN + IF NOT EXISTS( + SELECT NULL + FROM INFORMATION_SCHEMA.COLUMNS + WHERE + table_name = 'environment' + AND table_schema = 'infrastructure' + AND column_name = 'openshift_project_pattern' + ) THEN + ALTER TABLE `environment` + ADD `openshift_project_pattern` varchar(300); + END IF; + END; +$$ + DELIMITER ; -- If adding new procedures, add them to the bottom of this list @@ -1477,6 +1513,8 @@ CALL add_enum_webhook_to_type_in_project_notification(); CALL add_index_for_deployment_environment(); CALL add_index_for_task_environment(); CALL add_router_pattern_to_project(); +CALL add_openshift_to_environment(); +CALL add_openshift_project_pattern_to_environment(); -- Drop legacy SSH key procedures DROP PROCEDURE IF EXISTS CreateProjectSshKey; diff --git a/services/api-db/docker-entrypoint-initdb.d/03-procedures.sql b/services/api-db/docker-entrypoint-initdb.d/03-procedures.sql index a2f23ce633..b3e5a2b2e8 100644 --- a/services/api-db/docker-entrypoint-initdb.d/03-procedures.sql +++ b/services/api-db/docker-entrypoint-initdb.d/03-procedures.sql @@ -152,15 +152,17 @@ $$ CREATE OR REPLACE PROCEDURE CreateOrUpdateEnvironment ( - IN id int, - IN name varchar(100), - IN pid int, - IN deploy_type ENUM('branch', 'pullrequest', 'promote'), - IN deploy_base_ref varchar(100), - IN deploy_head_ref varchar(100), - IN deploy_title varchar(300), - IN environment_type ENUM('production', 'development'), - IN openshift_project_name varchar(100) + IN id int, + IN name varchar(100), + IN pid int, + IN deploy_type ENUM('branch', 'pullrequest', 'promote'), + IN deploy_base_ref varchar(100), + IN deploy_head_ref varchar(100), + IN deploy_title varchar(300), + IN environment_type ENUM('production', 'development'), + IN openshift_project_name varchar(100), + IN openshift int, + IN openshift_project_pattern varchar(300) ) BEGIN INSERT INTO environment ( @@ -173,6 +175,8 @@ CREATE OR REPLACE PROCEDURE deploy_title, environment_type, openshift_project_name, + openshift, + openshift_project_pattern, deleted ) SELECT @@ -185,6 +189,8 @@ CREATE OR REPLACE PROCEDURE deploy_title, environment_type, openshift_project_name, + openshift, + openshift_project_pattern, '0000-00-00 00:00:00' FROM project AS p diff --git a/services/api/src/resolvers.js b/services/api/src/resolvers.js index bf9342b23f..8b406e1c63 100644 --- a/services/api/src/resolvers.js +++ b/services/api/src/resolvers.js @@ -114,6 +114,18 @@ const { getEnvironmentUrl, } = require('./resources/environment/resolvers'); +const { + getDeployTargetConfigById, + getDeployTargetConfigsByProjectId, + getDeployTargetConfigsByDeployTarget, + // getDeployTargetConfigsByKubernetes, + addDeployTargetConfig, + deleteDeployTargetConfig, + updateDeployTargetConfig, + getAllDeployTargetConfigs, + deleteAllDeployTargetConfigs, +} = require('./resources/deploytargetconfig/resolvers'); + const { addNotificationMicrosoftTeams, addNotificationRocketChat, @@ -146,6 +158,8 @@ const { deleteOpenshift, getAllOpenshifts, getOpenshiftByProjectId, + getOpenshiftByDeployTargetId, + getOpenshiftByEnvironmentId, updateOpenshift, deleteAllOpenshifts, } = require('./resources/openshift/resolvers'); @@ -154,6 +168,7 @@ const { deleteProject, addProject, getProjectByName, + getProjectById, getProjectByGitUrl, getProjectByEnvironmentId, getProjectsByMetadata, @@ -308,6 +323,7 @@ const resolvers = { openshift: getOpenshiftByProjectId, kubernetes: getOpenshiftByProjectId, environments: getEnvironmentsByProjectId, + deployTargetConfigs: getDeployTargetConfigsByProjectId, envVariables: getEnvVarsByProjectId, groups: getGroupsByProjectId, }, @@ -328,6 +344,10 @@ const resolvers = { projects: getAllProjectsByGroupId, modifiers: getAllModifiersByGroupId, }, + DeployTargetConfig: { + project: getProjectById, + deployTarget: getOpenshiftByDeployTargetId, + }, Environment: { project: getProjectByEnvironmentId, deployments: getDeploymentsByEnvironmentId, @@ -342,6 +362,8 @@ const resolvers = { services: getEnvironmentServicesByEnvironmentId, problems: getProblemsByEnvironmentId, facts: getFactsByEnvironmentId, + openshift: getOpenshiftByEnvironmentId, + kubernetes: getOpenshiftByEnvironmentId, }, Fact: { references: getFactReferencesByFactId, @@ -433,6 +455,11 @@ const resolvers = { allProblemHarborScanMatchers: getProblemHarborScanMatches, projectsByMetadata: getProjectsByMetadata, projectsByFactSearch: getProjectsByFactSearch, + deployTargetConfigById: getDeployTargetConfigById, + deployTargetConfigsByProjectId: getDeployTargetConfigsByProjectId, + deployTargetConfigsByDeployTarget: getDeployTargetConfigsByDeployTarget, + // deployTargetConfigsByKubernetes: getDeployTargetConfigsByKubernetes, + allDeployTargetConfigs: getAllDeployTargetConfigs, }, Mutation: { addProblem, @@ -549,6 +576,10 @@ const resolvers = { updateBillingModifier, deleteBillingModifier, deleteAllBillingModifiersByBillingGroup, + deleteAllDeployTargetConfigs, + addDeployTargetConfig, + deleteDeployTargetConfig, + updateDeployTargetConfig, }, Subscription: { backupChanged: backupSubscriber, diff --git a/services/api/src/resources/deploytargetconfig/helpers.ts b/services/api/src/resources/deploytargetconfig/helpers.ts new file mode 100644 index 0000000000..4df4521f8e --- /dev/null +++ b/services/api/src/resources/deploytargetconfig/helpers.ts @@ -0,0 +1,29 @@ +import * as R from 'ramda'; +import { Pool } from 'mariadb'; +import { query } from '../../util/db'; +import { Sql } from './sql'; + +export const Helpers = (sqlClientPool: Pool) => { + const aliasOpenshiftToK8s = (deploytargetConfigs: any[]) => { + return deploytargetConfigs.map(deploytargetConfigs => { + return { + ...deploytargetConfigs, + kubernetesNamespaceName: deploytargetConfigs.deployTargetProjectPattern + }; + }); + }; + + const getDeployTargetConfigById = async (deploytargetID: number) => { + const rows = await query( + sqlClientPool, + Sql.selectDeployTargetConfigById(deploytargetID) + ); + const withK8s = aliasOpenshiftToK8s(rows); + return R.prop(0, withK8s); + }; + + return { + aliasOpenshiftToK8s, + getDeployTargetConfigById, + }; +}; diff --git a/services/api/src/resources/deploytargetconfig/resolvers.ts b/services/api/src/resources/deploytargetconfig/resolvers.ts new file mode 100644 index 0000000000..d80492aff4 --- /dev/null +++ b/services/api/src/resources/deploytargetconfig/resolvers.ts @@ -0,0 +1,255 @@ +import * as R from 'ramda'; +// @ts-ignore +import { sendToLagoonLogs } from '@lagoon/commons/dist/logs'; +// @ts-ignore +import { createRemoveTask } from '@lagoon/commons/dist/tasks'; +import { ResolverFn } from '../'; +import { isPatchEmpty, query, knex } from '../../util/db'; +import convertDateToMYSQLDateTimeFormat from '../../util/convertDateToMYSQLDateTimeFormat'; +import { Helpers } from './helpers'; +import { Sql } from './sql'; +import { Sql as projectSql } from '../project/sql'; +import { Helpers as projectHelpers } from '../project/helpers'; +import { logger } from '../../loggers/logger'; + + +export const getDeployTargetConfigById = async ( + root, + args, + { sqlClientPool, hasPermission } +) => { + const deployTargetConfig = await Helpers(sqlClientPool).getDeployTargetConfigById(args.id); + + if (!deployTargetConfig) { + return null; + } + + // only admin can add deployment targets for now + await hasPermission('project', `viewAll`); + + return deployTargetConfig; +}; + +export const getDeployTargetConfigsByProjectId: ResolverFn = async ( + project, + args, + { sqlClientPool, hasPermission, keycloakGrant, models } +) => { + + let pid = args.project; + if (project) { + pid = project.id; + } + + // only admin can add deployment targets for now + await hasPermission('project', `viewAll`); + + const rows = await query( + sqlClientPool, + `SELECT * + FROM deploy_target_config d + WHERE d.project = :pid ORDER BY d.weight DESC`, + { pid } + ); + const withK8s = Helpers(sqlClientPool).aliasOpenshiftToK8s(rows); + + return withK8s; +}; + +export const getDeployTargetConfigsByDeployTarget: ResolverFn = async ( + root, + args, + { sqlClientPool, hasPermission } +) => { + let oid = args.deployTarget; + + // only admin can add deployment targets for now + await hasPermission('project', `viewAll`); + + const rows = await query( + sqlClientPool, + `SELECT d.* + FROM + deploy_target_config d + WHERE d.deploy_target = :oid`, + { oid } + ); + + const withK8s = Helpers(sqlClientPool).aliasOpenshiftToK8s(rows); + return withK8s; +}; + +// export const getDeployTargetConfigsByKubernetes: ResolverFn = async ( +// root, +// args, +// ctx +// ) => +// getDeployTargetConfigsByDeployTarget( +// root, +// { +// ...args, +// openshift: args.kubernetes +// }, +// ctx +// ); + +export const addDeployTargetConfig: ResolverFn = async ( + root, + { input }, + { sqlClientPool, hasPermission, userActivityLogger } +) => { + + const deployTarget = input.deployTarget ; + if (!deployTarget) { + throw new Error('Must provide deployTarget field'); + } + const deployTargetProjectPattern = input.deployTargetProjectPattern; + + // only admin can add deployment targets for now + await hasPermission('project', `viewAll`); + + let id = input.id + let project = input.project + let weight = input.weight || 1 + let branches = input.branches || "true" + let pullrequests = input.pullrequests || "true" + + const { insertId } = await query( + sqlClientPool, + Sql.insertDeployTargetConfig({ + id, + project, + weight, + deployTarget, + deployTargetProjectPattern, + branches, + pullrequests + }) + ); + + const rows = await query(sqlClientPool, Sql.selectDeployTargetConfigById(insertId)); + + userActivityLogger.user_action(`User added DeployTargetConfig`, { + project: input.name || '', + event: 'api:addDeployTargetConfig', + payload: { + ...input + } + }); + +// const withK8s = Helpers(sqlClientPool).aliasOpenshiftToK8s([ +// R.path([0, 0], rows) +// ]); + const deployTargetConfig = rows[0]; + logger.info(`${JSON.stringify(deployTargetConfig)}`) + + return deployTargetConfig; +}; + +export const deleteDeployTargetConfig: ResolverFn = async ( + root, + { input: { id, project } }, + { sqlClientPool, hasPermission, userActivityLogger } +) => { + + // only admin can delete deployment targets for now + await hasPermission('project', `viewAll`); + logger.info(`${id} ${project}`) + try { + await query(sqlClientPool, 'DELETE FROM deploy_target_config WHERE id = :id', { + id, + project + }); + } catch (err) { + // Not allowed to stop execution. + } + + userActivityLogger.user_action(`User deleted DeployTargetConfig'`, { + project: project || '', + event: 'api:deleteEnvironment', + payload: { + id, + project, + } + }); + + return 'success'; +}; + +export const updateDeployTargetConfig: ResolverFn = async ( + root, + { input }, + { sqlClientPool, hasPermission, userActivityLogger } +) => { + if (isPatchEmpty(input)) { + throw new Error('input.patch requires at least 1 attribute'); + } + + const id = input.id; + const deployTarget = input.patch.deployTarget; + const deployTargetProjectPattern = input.patch.deployTargetNamespacePattern; + + // only admin can update deployment targets for now + await hasPermission('project', `viewAll`); + + await query( + sqlClientPool, + Sql.updateDeployTargetConfig({ + id, + patch: { + weight: input.patch.weight, + branches: input.patch.branches, + pullrequests: input.patch.pullrequests, + deployTarget, + deployTargetProjectPattern, + } + }) + ); + + const rows = await query(sqlClientPool, Sql.selectDeployTargetConfigById(id)); + const withK8s = Helpers(sqlClientPool).aliasOpenshiftToK8s(rows); + + userActivityLogger.user_action(`User updated DeployTargetConfig`, { + event: 'api:updateDeployTargetConfig', + payload: { + data: withK8s + } + }); + + return R.prop(0, withK8s); +}; + +export const getAllDeployTargetConfigs: ResolverFn = async ( + root, + { order }, + { sqlClientPool, hasPermission } +) => { + await hasPermission('project', 'viewAll'); + + let queryBuilder = knex('deploy_target_config'); + + const rows = await query(sqlClientPool, queryBuilder.toString()); + const withK8s = Helpers(sqlClientPool).aliasOpenshiftToK8s(rows); + return withK8s; +}; + +export const deleteAllDeployTargetConfigs: ResolverFn = async ( + root, + args, + { sqlClientPool, hasPermission, userActivityLogger } +) => { + await hasPermission('project', 'deleteAll'); + + await query(sqlClientPool, Sql.truncateDeployTargetConfigs()); + + userActivityLogger.user_action(`User deleted all deployTargetConfigs'`, { + project: '', + event: 'api:deleteAllDeployTargetConfigs', + payload: { + args + } + }); + + // TODO: Check rows for success + return 'success'; +}; diff --git a/services/api/src/resources/deploytargetconfig/sql.ts b/services/api/src/resources/deploytargetconfig/sql.ts new file mode 100644 index 0000000000..dacf3c78bb --- /dev/null +++ b/services/api/src/resources/deploytargetconfig/sql.ts @@ -0,0 +1,50 @@ +import { knex } from '../../util/db'; + +export const Sql = { + updateDeployTargetConfig: ({ id, patch }: { id: number, patch: { [key: string]: any } }) => { + const updatePatch = { + ...patch, + }; + + return knex('deploy_target_config') + .where('id', '=', id) + .update(updatePatch) + .toString(); + }, + selectDeployTargetConfigById: (id: number) => + knex('deploy_target_config') + .where('id', '=', id) + .toString(), + truncateDeployTargetConfigs: () => + knex('deploy_target_config') + .truncate() + .toString(), + insertDeployTargetConfig: ({ + id, + project, + weight, + deployTarget, + deployTargetProjectPattern, + branches, + pullrequests + }: { + id: number, + project: number, + weight: number, + deployTarget: number, + deployTargetProjectPattern: number, + branches: number, + pullrequests: string + }) => + knex('deploy_target_config') + .insert({ + id, + project, + weight, + deployTarget, + deployTargetProjectPattern, + branches, + pullrequests + }) + .toString(), +}; diff --git a/services/api/src/resources/environment/resolvers.ts b/services/api/src/resources/environment/resolvers.ts index bed78818dc..820990ca80 100644 --- a/services/api/src/resources/environment/resolvers.ts +++ b/services/api/src/resources/environment/resolvers.ts @@ -333,6 +333,29 @@ export const addOrUpdateEnvironment: ResolverFn = async ( project: pid }); + /* + check if an openshift is provided (this should be provided by any functions that call this within lagoon) + otherwise source the one from the project as a fall back + (should also check if one already exists from the environment and use that as a first preference) + */ + let openshift = input.kubernetes || input.openshift; + let openshiftProjectPattern = input.kubernetesNamespacePattern || input.openshiftProjectPattern; + + try { + const curEnv = await Helpers(sqlClientPool).getEnvironmentById(input.id); + openshift = curEnv.openshift + openshiftProjectPattern = curEnv.openshiftProjectPattern + } catch (err) { + // do nothing + } + const projectOpenshift = await projectHelpers(sqlClientPool).getProjectById(input.project); + if (!openshift) { + openshift = projectOpenshift.openshift + } + if (!openshiftProjectPattern) { + openshiftProjectPattern = projectOpenshift.openshiftProjectPattern + } + const rows = await query( sqlClientPool, `CALL CreateOrUpdateEnvironment( @@ -344,12 +367,16 @@ export const addOrUpdateEnvironment: ResolverFn = async ( :deploy_head_ref, :deploy_title, :environment_type, - :openshift_project_name + :openshift_project_name, + ${openshift ? ':openshift' : 'NULL'}, + ${openshiftProjectPattern ? ':openshift_project_pattern' : 'NULL'} );`, { ...inputDefaults, ...input, - openshiftProjectName + openshift, + openshiftProjectName, + openshiftProjectPattern } ); @@ -575,6 +602,7 @@ export const updateEnvironment: ResolverFn = async ( deployHeadRef: input.patch.deployHeadRef, deployTitle: input.patch.deployTitle, environmentType: input.patch.environmentType, + openshift: input.patch.openshift, openshiftProjectName, route: input.patch.route, routes: input.patch.routes, @@ -599,6 +627,7 @@ export const updateEnvironment: ResolverFn = async ( deployHeadRef: input.patch.deployHeadRef, deployTitle: input.patch.deployTitle, environmentType: input.patch.environmentType, + openshift: input.patch.openshift, openshiftProjectName, route: input.patch.route, routes: input.patch.routes, diff --git a/services/api/src/resources/openshift/resolvers.ts b/services/api/src/resources/openshift/resolvers.ts index 616b683e6e..5ce19615e5 100644 --- a/services/api/src/resources/openshift/resolvers.ts +++ b/services/api/src/resources/openshift/resolvers.ts @@ -86,6 +86,50 @@ export const getOpenshiftByProjectId: ResolverFn = async ( return rows ? attrFilter(hasPermission, rows[0]) : null; }; +export const getOpenshiftByDeployTargetId: ResolverFn = async ( + { id: did }, + args, + { sqlClientPool, hasPermission } +) => { + await hasPermission('openshift', 'viewAll'); + + const rows = await query( + sqlClientPool, + `SELECT o.* + FROM deploy_target_config d + JOIN openshift o ON o.id = d.deploy_target + WHERE d.id = :did + `, + { + did + } + ); + + return rows ? attrFilter(hasPermission, rows[0]) : null; +}; + +export const getOpenshiftByEnvironmentId: ResolverFn = async ( + { id: eid }, + args, + { sqlClientPool, hasPermission } +) => { + await hasPermission('openshift', 'viewAll'); + + const rows = await query( + sqlClientPool, + `SELECT o.* + FROM environment e + JOIN openshift o ON o.id = e.openshift + WHERE e.id = :eid + `, + { + eid + } + ); + + return rows ? attrFilter(hasPermission, rows[0]) : null; +}; + export const updateOpenshift: ResolverFn = async ( root, { input }, diff --git a/services/api/src/resources/project/resolvers.ts b/services/api/src/resources/project/resolvers.ts index 9ac1077181..ee709710ed 100644 --- a/services/api/src/resources/project/resolvers.ts +++ b/services/api/src/resources/project/resolvers.ts @@ -103,6 +103,38 @@ export const getProjectByEnvironmentId: ResolverFn = async ( } }; +export const getProjectById: ResolverFn = async ( + { project: pid }, + args, + { sqlClientPool, hasPermission } +) => { + const rows = await query( + sqlClientPool, + `SELECT p.* + FROM project p + WHERE p.id = :pid + LIMIT 1`, + { pid } + ); + const withK8s = Helpers(sqlClientPool).aliasOpenshiftToK8s(rows); + + const project = withK8s[0]; + + await hasPermission('project', 'view', { + project: project.id + }); + + try { + await hasPermission('project', 'viewPrivateKey', { + project: project.id + }); + + return project; + } catch (err) { + return removePrivateKey(project); + } +}; + export const getProjectByGitUrl: ResolverFn = async ( root, args, diff --git a/services/api/src/typeDefs.js b/services/api/src/typeDefs.js index a0fdf254d2..aeed48048b 100644 --- a/services/api/src/typeDefs.js +++ b/services/api/src/typeDefs.js @@ -123,13 +123,13 @@ const typeDefs = gql` URL SEMVER } - + enum TaskPermission { MAINTAINER DEVELOPER GUEST } - + scalar SeverityScore type AdvancedTaskDefinitionArgument { @@ -698,6 +698,10 @@ const typeDefs = gql` Metadata key/values stored against a project """ metadata: JSON + """ + DeployTargetConfigs are a way to define which deploy targets are used for a project + """ + deployTargetConfigs: [DeployTargetConfig] } """ @@ -790,6 +794,10 @@ const typeDefs = gql` services: [EnvironmentService] problems(severity: [ProblemSeverityRating], source: [String]): [Problem] facts(keyFacts: Boolean): [Fact] + openshift: Openshift + openshiftProjectPattern: String + kubernetes: Kubernetes + kubernetesNamespacePattern: String } type EnvironmentHitsMonth { @@ -916,6 +924,45 @@ const typeDefs = gql` environments: [Environment] } + type DeployTargetConfig { + id: Int + project: Project + weight: Int + branches: String + pullrequests: String + deployTarget: Openshift + deployTargetProjectPattern: String + } + + input AddDeployTargetConfigInput { + id: Int + project: Int! + weight: Int + branches: String + pullrequests: String + deployTarget: Int + deployTargetProjectPattern: String + } + + input UpdateDeployTargetConfigPatchInput { + weight: Int + branches: String + pullrequests: String + deployTarget: Int + deployTargetProjectPattern: String + } + + input UpdateDeployTargetConfigInput { + id: Int! + patch: UpdateDeployTargetConfigPatchInput + } + + input DeleteDeployTargetConfigInput { + id: Int! + project: Int! + execute: Boolean + } + input DeleteEnvironmentInput { name: String! project: String! @@ -1062,7 +1109,10 @@ const typeDefs = gql` Returns a AdvancedTaskDefinitionArgument by Id """ advancedTaskDefinitionArgumentById(id: Int!) : [AdvancedTaskDefinitionArgument] - + deployTargetConfigById(id: Int!) : DeployTargetConfig + deployTargetConfigsByProjectId(project: Int!) : [DeployTargetConfig] + deployTargetConfigsByDeployTarget(deployTarget: Int!) : [DeployTargetConfig] + allDeployTargetConfigs: [DeployTargetConfig] } # Must provide id OR name @@ -1144,6 +1194,10 @@ const typeDefs = gql` environmentType: EnvType! openshiftProjectName: String kubernetesNamespaceName: String + openshift: Int + openshiftProjectPattern: String + kubernetes: Int + kubernetesNamespacePattern: String } input AddOrUpdateEnvironmentStorageInput { @@ -1556,6 +1610,10 @@ const typeDefs = gql` routes: String monitoringUrls: String autoIdle: Int + openshift: Int + openshiftProjectPattern: String + kubernetes: Int + kubernetesNamespacePattern: String """ Timestamp in format 'YYYY-MM-DD hh:mm:ss' """ @@ -1927,6 +1985,10 @@ const typeDefs = gql` updateBillingModifier(input: UpdateBillingModifierInput!): BillingModifier deleteBillingModifier(input: DeleteBillingModifierInput!): String deleteAllBillingModifiersByBillingGroup(input: GroupInput!): String + addDeployTargetConfig(input: AddDeployTargetConfigInput!): DeployTargetConfig + updateDeployTargetConfig(input: UpdateDeployTargetConfigInput!): DeployTargetConfig + deleteDeployTargetConfig(input: DeleteDeployTargetConfigInput!): String + deleteAllDeployTargetConfigs: String } type Subscription { diff --git a/tests/tests/vars/test_vars.yaml b/tests/tests/vars/test_vars.yaml index a56ef8fca4..fb6bb96cf5 100644 --- a/tests/tests/vars/test_vars.yaml +++ b/tests/tests/vars/test_vars.yaml @@ -1,10 +1,10 @@ --- -webhook_url: "$WEBHOOK_PROTOCOL://$WEBHOOK_HOST:$WEBHOOK_PORT" -graphql_url: "$API_PROTOCOL://$API_HOST:$API_PORT/graphql" -localgit_url: "ssh://git@$GIT_HOST:$GIT_PORT/git" -ssh_host: "$SSH_HOST" -ssh_auth_port: "$SSH_PORT" -cluster_type: "$CLUSTER_TYPE" -route_suffix: "$ROUTE_SUFFIX_HTTP:$ROUTE_SUFFIX_HTTP_PORT" -route_suffix_https: "$ROUTE_SUFFIX_HTTPS:$ROUTE_SUFFIX_HTTPS_PORT" -del_status_code: "$DELETED_STATUS_CODE" \ No newline at end of file +webhook_url: "http://webhook-handler:3000" +graphql_url: "http://api:3000/graphql" +localgit_url: "ssh://git@:/git" +ssh_host: "ssh" +ssh_auth_port: "2020" +cluster_type: "control-k8s" +route_suffix: "172.17.0.1.nip.io:18080" +route_suffix_https: "172.17.0.1.nip.io:18443" +del_status_code: "404" \ No newline at end of file