<file_sep>const R = require('ramda');
const {
  ifNotAdmin,
  inClause,
  inClauseOr,
  isPatchEmpty,
  knex,
  prepare,
  query,
  whereAnd,
} = require('./utils');

const Sql = {
  updateEnvironment: (cred, input) => {
    const { name, patch } = input;

    return knex('environment')
      .where('name', '=', name)
      .update(patch)
      .toString();
  },
  selectEnvironmentByName: name =>
    knex('environment')
      .where('name', '=', name)
      .toString(),
};

const getEnvironmentsByProjectId = sqlClient => async (cred, pid, args) => {
  const { projects } = cred.permissions;

  if (cred.role !== 'admin' && !R.contains(pid, projects)) {
    throw new Error('Unauthorized');
  }

  const prep = prepare(
    sqlClient,
    `SELECT
        *
      FROM environment e
      WHERE e.project = :pid
      ${args.type ? 'AND e.environment_type = :type' : ''}
    `,
  );

  const rows = await query(sqlClient, prep({ pid: pid, type: args.type }));

  return rows;
};

const getEnvironmentByOpenshiftProjectName = sqlClient => async (cred, args) => {
  const { customers, projects } = cred.permissions;
  const str = `
      SELECT
        e.*
      FROM environment e
        JOIN project p ON e.project = p.id
        JOIN customer c ON p.customer = c.id
      WHERE e.openshift_projectname = :openshiftProjectName
      ${ifNotAdmin(
        cred.role,
        `AND (${inClauseOr([
          ['c.id', customers],
          ['p.id', projects],
        ])})`,
      )}
    `;

  const prep = prepare(sqlClient, str);

  const rows = await query(sqlClient, prep(args));

  return rows[0];
};

const addOrUpdateEnvironment = sqlClient => async (cred, input) => {
  const { projects } = cred.permissions;
  const pid = input.project.toString();

  if (cred.role !== 'admin' && !R.contains(pid, projects)) {
    throw new Error('Project creation unauthorized.');
  }
  const prep = prepare(
    sqlClient,
    `CALL CreateOrUpdateEnvironment(
        :name,
        :project,
        :deploy_type,
        :environment_type,
        :openshift_projectname
      );
    `,
  );

  const rows = await query(sqlClient, prep(input));
  const environment = R.path([0, 0], rows);

  return environment;
};

const deleteEnvironment = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized');
  }

  const prep = prepare(sqlClient, 'CALL DeleteEnvironment(:name, :project)');
  const rows = await query(sqlClient, prep(input));

  // TODO: maybe check rows for changed result
  return 'success';
};

const updateEnvironment = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized');
  }

  if (isPatchEmpty(input)) {
    throw new Error('input.patch requires at least 1 attribute');
  }

  const name = input.name;
  await query(sqlClient, Sql.updateEnvironment(cred, input));

  const rows = await query(sqlClient, Sql.selectEnvironmentByName(name));

  return R.prop(0, rows);
};

const getAllEnvironments = sqlClient => async (cred, args) => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized');
  }

  const where = whereAnd([
    args.createdAfter ? 'created >= :createdAfter' : '',
  ]);

  const prep = prepare(sqlClient, `SELECT * FROM environment ${where}`);
  const rows = await query(sqlClient, prep(args));
  return rows;
};

const Queries = {
  addOrUpdateEnvironment,
  getEnvironmentByOpenshiftProjectName,
  deleteEnvironment,
  getEnvironmentsByProjectId,
  updateEnvironment,
  getAllEnvironments,
};

module.exports = {
  Sql,
  Queries,
};
<file_sep>ARG IMAGE_REPO
FROM ${IMAGE_REPO:-lagoon}/oc

# the oc image comes with an HOME=/home which is needed to run as unpriviledged, but oc-build-deploy-dind will run as root
RUN rm -rf /root && ln -s /home /root
ENV LAGOON=oc-build-deploy-dind

RUN	mkdir -p /git

WORKDIR /git

COPY docker-entrypoint.sh /lagoon/entrypoints/100-docker-entrypoint.sh
COPY build-deploy.sh /usr/sbin/build-deploy
COPY build-deploy-docker-compose.sh /build-deploy-docker-compose.sh

COPY scripts /scripts

COPY openshift-templates /openshift-templates

CMD ["build-deploy"]
<file_sep>#!/bin/sh -v
# This script calls the /ping endpoing of the php-fpm, if the return code is 0, the php-fpm has correctly started
SCRIPT_NAME=/${1:-ping} SCRIPT_FILENAME=/${1:-ping} REQUEST_METHOD=GET /usr/bin/cgi-fcgi -bind -connect 127.0.0.1:9000<file_sep>ARG IMAGE_REPO
FROM ${IMAGE_REPO:-lagoon}/elasticsearch

COPY conf/elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml
<file_sep>#!/bin/bash

ep /etc/varnish/*

exec "$@"
<file_sep>#!/bin/sh
set -e

if [ -f /var/run/secrets/lagoon/sshkey/ssh-privatekey ]; then
  cp /var/run/secrets/lagoon/sshkey/ssh-privatekey /home/.ssh/id_rsa
  # add a new line to the key. OpenSSH is very picky that keys are always end with a newline
  echo >> /home/.ssh/id_rsa

  chmod 400 /home/.ssh/id_rsa
  eval $(ssh-agent -a /tmp/ssh-agent)
  ssh-add /home/.ssh/id_rsa
fi

# Test if pygmy or cachalot ssh-agents are mounted and symlink them as our known ssh-auth-sock file.
# This will only be used in local development
if [ -S /tmp/amazeeio_ssh-agent/socket ]; then
  ln -s /tmp/amazeeio_ssh-agent/socket $SSH_AUTH_SOCK
fi<file_sep>ARG IMAGE_REPO
FROM ${IMAGE_REPO:-lagoon}/commons as commons
FROM alpine

MAINTAINER amazee.io
ENV LAGOON=varnish

# Copying commons files
COPY --from=commons /lagoon /lagoon
COPY --from=commons /bin/fix-permissions /bin/ep /bin/docker-sleep /bin/
COPY --from=commons /home /home

# When Bash is invoked via `sh` it behaves like the old Bourne Shell and sources a file that is given in `ENV`
# When Bash is invoked as non-interactive (like `bash -c command`) it sources a file that is given in `BASH_ENV`
ENV TMPDIR=/tmp TMP=/tmp HOME=/home ENV=/home/.bashrc BASH_ENV=/home/.bashrc

RUN apk --no-cache add varnish

RUN echo "${VARNISH_SECRET:-lagoon_default_secret}" >> /etc/varnish/secret

COPY default.vcl /etc/varnish/default.vcl

RUN fix-permissions /etc/varnish/ \
    && fix-permissions /var/run/ \
    && fix-permissions /var/lib/varnish

COPY docker-entrypoint /lagoon/entrypoints/70-varnish-entrypoint

EXPOSE 8080

# tells the local development environment on which port we are running
ENV LAGOON_LOCALDEV_HTTP_PORT=8080

ENTRYPOINT ["/lagoon/entrypoints.sh"]
CMD ["varnishd", "-a", ":8080", "-T", ":6082", "-F", "-f", "/etc/varnish/default.vcl", "-S", "/etc/varnish/secret"]
<file_sep>const { Sql } = require('./sshKey');

describe('Sql', () => {
  describe('updateSshKey', () => {
    it('should create a proper query', () => {
      const cred = {};
      const input = {
        id: 1,
        patch: {
          keyType: 'ssh-rsa',
        },
      };
      const ret = Sql.updateSshKey(cred, input);
      expect(ret).toMatchSnapshot();
    });
  });
  describe('selectSshKey', () => {
    it('should create a proper query', () => {
      const ret = Sql.selectSshKey(1);
      expect(ret).toMatchSnapshot();
    });
  });
});
<file_sep>#!/bin/bash

containsValue () {
  local e
  for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
  return 1
}

##############################################
### PREPARATION
##############################################

# Load path of docker-compose that should be used
DOCKER_COMPOSE_YAML=($(cat .lagoon.yml | shyaml get-value docker-compose-yaml))

# Load all Services that are defined
SERVICES=($(cat $DOCKER_COMPOSE_YAML | shyaml keys services))

# Figure out which services should we handle
SERVICE_TYPES=()
IMAGES=()
for SERVICE in "${SERVICES[@]}"
do
  # The name of the service can be overridden, if not we use the actual servicename
  SERVICE_NAME=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.name default)
  if [ "$SERVICE_NAME" == "default" ]; then
    SERVICE_NAME=$SERVICE
  fi

  # Load the servicetype. If it's "none" we will not care about this service at all
  SERVICE_TYPE=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.type custom)

  # Allow the servicetype to be overriden by environment in .lagoon.yml
  ENVIRONMENT_SERVICE_TYPE_OVERRIDE=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH}.types.$SERVICE false)
  if [ ! $ENVIRONMENT_SERVICE_TYPE_OVERRIDE == "false" ]; then
    SERVICE_TYPE=$ENVIRONMENT_SERVICE_TYPE_OVERRIDE
  fi

  if [ "$SERVICE_TYPE" == "none" ]; then
    continue
  fi

  # We build all images
  IMAGES+=("${SERVICE}")

  # Create an array with all Service Types, Names and Original Service Name
  SERVICE_TYPES+=("${SERVICE_TYPE}:${SERVICE_NAME}:${SERVICE}")
done

##############################################
### BUILD IMAGES
##############################################

# we only need to build images for pullrequests and branches
if [ "$TYPE" == "pullrequest" ] || [ "$TYPE" == "branch" ]; then

  BUILD_ARGS=()
  BUILD_ARGS+=(--build-arg IMAGE_REPO="${CI_OVERRIDE_IMAGE_REPO}")
  BUILD_ARGS+=(--build-arg LAGOON_GIT_SHA="${LAGOON_GIT_SHA}")
  BUILD_ARGS+=(--build-arg LAGOON_GIT_BRANCH="${BRANCH}")
  BUILD_ARGS+=(--build-arg LAGOON_PROJECT="${PROJECT}")
  BUILD_ARGS+=(--build-arg LAGOON_BUILD_TYPE="${TYPE}")

  if [ "$TYPE" == "pullrequest" ]; then
    BUILD_ARGS+=(--build-arg LAGOON_PR_HEAD_BRANCH="${PR_HEAD_BRANCH}")
    BUILD_ARGS+=(--build-arg LAGOON_PR_HEAD_SHA="${PR_HEAD_SHA}")
    BUILD_ARGS+=(--build-arg LAGOON_PR_BASE_BRANCH="${PR_BASE_BRANCH}")
    BUILD_ARGS+=(--build-arg LAGOON_PR_BASE_SHA="${PR_BASE_SHA}")
    BUILD_ARGS+=(--build-arg LAGOON_PR_TITLE="${PR_TITLE}")
  fi

  for IMAGE_NAME in "${IMAGES[@]}"
  do
    # We need the Image Name uppercase sometimes, so we create that here
    IMAGE_NAME_UPPERCASE=$(echo "$IMAGE_NAME" | tr '[:lower:]' '[:upper:]')

    # To prevent clashes of ImageNames during parallel builds, we give all Images a Temporary name
    TEMPORARY_IMAGE_NAME="${OPENSHIFT_PROJECT}-${IMAGE_NAME}"

    DOCKERFILE=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$IMAGE_NAME.build.dockerfile false)
    if [ $DOCKERFILE == "false" ]; then
      # No Dockerfile defined, assuming to download the Image directly

      PULL_IMAGE=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$IMAGE_NAME.image false)
      if [ $PULL_IMAGE == "false" ]; then
        echo "No Dockerfile or Image for service ${IMAGE_NAME} defined"; exit 1;
      fi

      # allow to overwrite image that we pull
      OVERRIDE_IMAGE=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$IMAGE_NAME.labels.lagoon\\.image false)
      if [ ! $OVERRIDE_IMAGE == "false" ]; then
        # expand environment variables from ${OVERRIDE_IMAGE}
        PULL_IMAGE=$(echo "${OVERRIDE_IMAGE}" | envsubst)
      fi

      . /scripts/exec-pull-tag.sh

    else
      # Dockerfile defined, load the context and build it

      BUILD_CONTEXT=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$IMAGE_NAME.build.context .)
      if [ ! -f $BUILD_CONTEXT/$DOCKERFILE ]; then
        echo "defined Dockerfile $DOCKERFILE for service $IMAGE_NAME not found"; exit 1;
      fi

      . /scripts/exec-build.sh
    fi

    # adding the build image to the list of arguments passed into the next image builds
    BUILD_ARGS+=(--build-arg ${IMAGE_NAME_UPPERCASE}_IMAGE=${TEMPORARY_IMAGE_NAME})
  done

fi

##############################################
### CREATE OPENSHIFT SERVICES AND ROUTES
##############################################

for SERVICE_TYPES_ENTRY in "${SERVICE_TYPES[@]}"
do
  echo "=== BEGIN route processing for service ${SERVICE_TYPES_ENTRY} ==="
  echo "=== OPENSHIFT_SERVICES_TEMPLATE=${OPENSHIFT_SERVICES_TEMPLATE} "
  IFS=':' read -ra SERVICE_TYPES_ENTRY_SPLIT <<< "$SERVICE_TYPES_ENTRY"


  SERVICE_TYPE=${SERVICE_TYPES_ENTRY_SPLIT[0]}
  SERVICE_NAME=${SERVICE_TYPES_ENTRY_SPLIT[1]}
  SERVICE=${SERVICE_TYPES_ENTRY_SPLIT[2]}


  SERVICE_TYPE_OVERRIDE=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH}.types.$SERVICE false)
  if [ ! $SERVICE_TYPE_OVERRIDE == "false" ]; then
    SERVICE_TYPE=$SERVICE_TYPE_OVERRIDE
  fi

  OPENSHIFT_SERVICES_TEMPLATE="/openshift-templates/${SERVICE_TYPE}/services.yml"

  if [ -f $OPENSHIFT_SERVICES_TEMPLATE ]; then
    OPENSHIFT_TEMPLATE=$OPENSHIFT_SERVICES_TEMPLATE
    . /scripts/exec-openshift-resources.sh
  fi

  OPENSHIFT_ROUTES_TEMPLATE="/openshift-templates/${SERVICE_TYPE}/routes.yml"
  if [ -f $OPENSHIFT_ROUTES_TEMPLATE ]; then

    # The very first generated route is set as MAIN_GENERATED_ROUTE
    if [ -z "${MAIN_GENERATED_ROUTE+x}" ]; then
      MAIN_GENERATED_ROUTE=$SERVICE_NAME
    fi

    OPENSHIFT_TEMPLATE=$OPENSHIFT_ROUTES_TEMPLATE
    . /scripts/exec-openshift-resources.sh
  fi
done

##############################################
### CUSTOM ROUTES FROM .lagoon.yml
##############################################

# Two while loops as we have multiple services that want routes and each service has multiple routes
ROUTES_SERVICE_COUNTER=0
while [ -n "$(cat .lagoon.yml | shyaml keys environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER 2> /dev/null)" ]; do
  ROUTES_SERVICE=$(cat .lagoon.yml | shyaml keys environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER)

  ROUTE_DOMAIN_COUNTER=0
  while [ -n "$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER.$ROUTES_SERVICE.$ROUTE_DOMAIN_COUNTER 2> /dev/null)" ]; do
    # Routes can either be a key (when the have additional settings) or just a value
    if cat .lagoon.yml | shyaml keys environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER.$ROUTES_SERVICE.$ROUTE_DOMAIN_COUNTER &> /dev/null; then
      ROUTE_DOMAIN=$(cat .lagoon.yml | shyaml keys environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER.$ROUTES_SERVICE.$ROUTE_DOMAIN_COUNTER)
      # Route Domains include dots, which need to be esacped via `\.` in order to use them within shyaml
      ROUTE_DOMAIN_ESCAPED=$(cat .lagoon.yml | shyaml keys environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER.$ROUTES_SERVICE.$ROUTE_DOMAIN_COUNTER | sed 's/\./\\./g')
      ROUTE_TLS_ACME=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER.$ROUTES_SERVICE.$ROUTE_DOMAIN_COUNTER.$ROUTE_DOMAIN_ESCAPED.tls-acme true)
      ROUTE_INSECURE=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER.$ROUTES_SERVICE.$ROUTE_DOMAIN_COUNTER.$ROUTE_DOMAIN_ESCAPED.insecure Redirect)
    else
      # Only a value given, assuming some defaults
      ROUTE_DOMAIN=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.routes.$ROUTES_SERVICE_COUNTER.$ROUTES_SERVICE.$ROUTE_DOMAIN_COUNTER)
      ROUTE_TLS_ACME=true
      ROUTE_INSECURE=Redirect
    fi

    # The very first found route is set as MAIN_CUSTOM_ROUTE
    if [ -z "${MAIN_CUSTOM_ROUTE+x}" ]; then
      MAIN_CUSTOM_ROUTE=$ROUTE_DOMAIN
    fi

    ROUTE_SERVICE=$ROUTES_SERVICE

    . /scripts/exec-openshift-create-route.sh

    let ROUTE_DOMAIN_COUNTER=ROUTE_DOMAIN_COUNTER+1
  done

  let ROUTES_SERVICE_COUNTER=ROUTES_SERVICE_COUNTER+1
done


##############################################
### PROJECT WIDE ENV VARIABLES
##############################################

# If we have a custom route, we use that as main route
if [ "$MAIN_CUSTOM_ROUTE" ]; then
  MAIN_ROUTE_NAME=$MAIN_CUSTOM_ROUTE
# no custom route, we use the first generated route
elif [ "$MAIN_GENERATED_ROUTE" ]; then
  MAIN_ROUTE_NAME=$MAIN_GENERATED_ROUTE
fi

# Load the found main routes with correct schema
if [ "$MAIN_ROUTE_NAME" ]; then
  ROUTE=$(oc --insecure-skip-tls-verify -n ${OPENSHIFT_PROJECT} get route "$MAIN_ROUTE_NAME" -o=go-template --template='{{if .spec.tls.termination}}https://{{else}}http://{{end}}{{.spec.host}}')
fi

# Load all routes with correct schema and comma separated
ROUTES=$(oc --insecure-skip-tls-verify -n ${OPENSHIFT_PROJECT} get routes -o=go-template --template='{{range $index, $route := .items}}{{if $index}},{{end}}{{if $route.spec.tls.termination}}https://{{else}}http://{{end}}{{$route.spec.host}}{{end}}')

# Generate a Config Map with project wide env variables
oc process --insecure-skip-tls-verify \
  -n ${OPENSHIFT_PROJECT} \
  -f /openshift-templates/configmap.yml \
  -p NAME="lagoon-env" \
  -p SAFE_BRANCH="${SAFE_BRANCH}" \
  -p SAFE_PROJECT="${SAFE_PROJECT}" \
  -p BRANCH="${BRANCH}" \
  -p PROJECT="${PROJECT}" \
  -p ENVIRONMENT_TYPE="${ENVIRONMENT_TYPE}" \
  -p ROUTE="${ROUTE}" \
  -p ROUTES="${ROUTES}" \
  | oc apply --insecure-skip-tls-verify -n ${OPENSHIFT_PROJECT} -f -

if [ "$TYPE" == "pullrequest" ]; then
  oc patch --insecure-skip-tls-verify \
    -n ${OPENSHIFT_PROJECT} \
    configmap lagoon-env \
    -p "{\"data\":{\"LAGOON_PR_HEAD_BRANCH\":\"${PR_HEAD_BRANCH}\", \"LAGOON_PR_BASE_BRANCH\":\"${PR_BASE_BRANCH}\", \"LAGOON_PR_TITLE\":\"${PR_TITLE}\"}}"
fi

##############################################
### PUSH IMAGES TO OPENSHIFT REGISTRY
##############################################

if [ "$TYPE" == "pullrequest" ] || [ "$TYPE" == "branch" ]; then
  for IMAGE_NAME in "${IMAGES[@]}"
  do
    # Before the push the temporary name is resolved to the future tag with the registry in the image name
    TEMPORARY_IMAGE_NAME="${OPENSHIFT_PROJECT}-${IMAGE_NAME}"
    . /scripts/exec-push.sh
  done
elif [ "$TYPE" == "promote" ]; then

  for IMAGE_NAME in "${IMAGES[@]}"
  do
    . /scripts/exec-openshift-tag.sh
  done

fi

##############################################
### CREATE PVC, DEPLOYMENTS AND CRONJOBS
##############################################

for SERVICE_TYPES_ENTRY in "${SERVICE_TYPES[@]}"
do
  IFS=':' read -ra SERVICE_TYPES_ENTRY_SPLIT <<< "$SERVICE_TYPES_ENTRY"

  SERVICE_TYPE=${SERVICE_TYPES_ENTRY_SPLIT[0]}
  SERVICE_NAME=${SERVICE_TYPES_ENTRY_SPLIT[1]}
  SERVICE=${SERVICE_TYPES_ENTRY_SPLIT[2]}

  # Some Templates need additonal Parameters, like where persistent storage can be found.
  TEMPLATE_PARAMETERS=()
  PERSISTENT_STORAGE_PATH=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.persistent false)

  if [ ! $PERSISTENT_STORAGE_PATH == "false" ]; then
    TEMPLATE_PARAMETERS+=(-p PERSISTENT_STORAGE_PATH="${PERSISTENT_STORAGE_PATH}")

    PERSISTENT_STORAGE_CLASS=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.persistent\\.class false)
    if [ ! $PERSISTENT_STORAGE_CLASS == "false" ]; then
      TEMPLATE_PARAMETERS+=(-p PERSISTENT_STORAGE_CLASS="${PERSISTENT_STORAGE_CLASS}")
    fi

    PERSISTENT_STORAGE_NAME=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.persistent\\.name false)
    if [ ! $PERSISTENT_STORAGE_NAME == "false" ]; then
      TEMPLATE_PARAMETERS+=(-p PERSISTENT_STORAGE_NAME="${PERSISTENT_STORAGE_NAME}")
    fi

    PERSISTENT_STORAGE_SIZE=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.persistent\\.size false)
    if [ ! $PERSISTENT_STORAGE_SIZE == "false" ]; then
      TEMPLATE_PARAMETERS+=(-p PERSISTENT_STORAGE_SIZE="${PERSISTENT_STORAGE_SIZE}")
    fi
  fi

  DEPLOYMENT_STRATEGY=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.deployment\\.strategy false)
  if [ ! $DEPLOYMENT_STRATEGY == "false" ]; then
    TEMPLATE_PARAMETERS+=(-p DEPLOYMENT_STRATEGY="${DEPLOYMENT_STRATEGY}")
  fi

  # Generate PVC if service type defines one
  OPENSHIFT_SERVICES_TEMPLATE="/openshift-templates/${SERVICE_TYPE}/pvc.yml"
  if [ -f $OPENSHIFT_SERVICES_TEMPLATE ]; then
    OPENSHIFT_TEMPLATE=$OPENSHIFT_SERVICES_TEMPLATE
    . /scripts/exec-openshift-create-pvc.sh
  fi

  OPENSHIFT_TEMPLATE="/openshift-templates/${SERVICE_TYPE}/deployment.yml"
  OVERRIDE_TEMPLATE=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.$SERVICE.labels.lagoon\\.template false)
  if [ "${OVERRIDE_TEMPLATE}" == "false" ]; then

    if [ -f $OPENSHIFT_TEMPLATE ]; then
      . /scripts/exec-openshift-create-deployment.sh
    fi
  else
    OPENSHIFT_TEMPLATE=$OVERRIDE_TEMPLATE
    if [ ! -f $OPENSHIFT_TEMPLATE ]; then
      echo "defined template $OPENSHIFT_TEMPLATE for service $SERVICE_TYPE not found"; exit 1;
    else
      . /scripts/exec-openshift-create-deployment.sh
    fi
  fi

  # Generate cronjobs if service type defines them
  OPENSHIFT_SERVICES_TEMPLATE="/openshift-templates/${SERVICE_TYPE}/cronjobs.yml"
  if [ -f $OPENSHIFT_SERVICES_TEMPLATE ]; then
    OPENSHIFT_TEMPLATE=$OPENSHIFT_SERVICES_TEMPLATE
    . /scripts/exec-openshift-resources.sh
  fi

  ### CUSTOM CRONJOBS

  # Save the current deployment template parameters so we can reuse them for cronjobs
  DEPLOYMENT_TEMPLATE_PARAMETERS=("${TEMPLATE_PARAMETERS[@]}")

  CRONJOB_COUNTER=0
  while [ -n "$(cat .lagoon.yml | shyaml keys environments.${BRANCH//./\\.}.cronjobs.$CRONJOB_COUNTER 2> /dev/null)" ]
  do

    CRONJOB_SERVICE=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.cronjobs.$CRONJOB_COUNTER.service)

    # Only implement the cronjob for the services we are currently handling
    if [ $CRONJOB_SERVICE == $SERVICE ]; then

      # loading original $TEMPLATE_PARAMETERS as multiple cronjobs use the same values
      TEMPLATE_PARAMETERS=("${DEPLOYMENT_TEMPLATE_PARAMETERS[@]}")

      # Creating a save name (special characters removed )
      CRONJOB_NAME=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.cronjobs.$CRONJOB_COUNTER.name | sed "s/[^[:alnum:]-]/-/g" | sed "s/^-//g")
      CRONJOB_SCHEDULE=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.cronjobs.$CRONJOB_COUNTER.schedule)
      CRONJOB_COMMAND=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.cronjobs.$CRONJOB_COUNTER.command)

      TEMPLATE_PARAMETERS+=(-p CRONJOB_NAME="${CRONJOB_NAME}")
      TEMPLATE_PARAMETERS+=(-p CRONJOB_COMMAND="${CRONJOB_COMMAND}")

      # Convert the Cronjob Schedule for additional features and better spread
      CRONJOB_SCHEDULE=$(/scripts/convert-crontab.sh "$CRONJOB_SCHEDULE")
      TEMPLATE_PARAMETERS+=(-p CRONJOB_SCHEDULE="${CRONJOB_SCHEDULE}")

      OPENSHIFT_TEMPLATE="/openshift-templates/${SERVICE_TYPE}/custom-cronjob.yml"
      if [ ! -f $OPENSHIFT_TEMPLATE ]; then
        echo "No cronjob Template for service type ${SERVICE_TYPE} found"; exit 1;
      fi

      . /scripts/exec-openshift-resources.sh
    fi

    let CRONJOB_COUNTER=CRONJOB_COUNTER+1
  done

done


##############################################
### WAIT FOR POST-ROLLOUT TO BE FINISHED
##############################################

for SERVICE_TYPES_ENTRY in "${SERVICE_TYPES[@]}"
do

  IFS=':' read -ra SERVICE_TYPES_ENTRY_SPLIT <<< "$SERVICE_TYPES_ENTRY"

  SERVICE_TYPE=${SERVICE_TYPES_ENTRY_SPLIT[0]}
  SERVICE_NAME=${SERVICE_TYPES_ENTRY_SPLIT[1]}

  SERVICE_ROLLOUT_TYPE=$(cat $DOCKER_COMPOSE_YAML | shyaml get-value services.${SERVICE_NAME}.labels.lagoon\\.rollout deploymentconfigs)

  # if mariadb-galera is a statefulset check also for maxscale
  if [ $SERVICE_TYPE == "mariadb-galera" ]; then

    STATEFULSET="${SERVICE_NAME}-galera"
    . /scripts/exec-monitor-statefulset.sh

    SERVICE_NAME="${SERVICE_NAME}-maxscale"
    . /scripts/exec-monitor-deploy.sh

  elif [ ! $SERVICE_ROLLOUT_TYPE == "false" ]; then
    . /scripts/exec-monitor-deploy.sh
  fi
done


##############################################
### RUN POST-ROLLOUT tasks defined in .lagoon.yml
##############################################

COUNTER=0
while [ -n "$(cat .lagoon.yml | shyaml keys tasks.post-rollout.$COUNTER 2> /dev/null)" ]
do
  TASK_TYPE=$(cat .lagoon.yml | shyaml keys tasks.post-rollout.$COUNTER)
  echo $TASK_TYPE
  case "$TASK_TYPE" in
    run)
        COMMAND=$(cat .lagoon.yml | shyaml get-value tasks.post-rollout.$COUNTER.$TASK_TYPE.command)
        SERVICE_NAME=$(cat .lagoon.yml | shyaml get-value tasks.post-rollout.$COUNTER.$TASK_TYPE.service)
        CONTAINER=$(cat .lagoon.yml | shyaml get-value tasks.post-rollout.$COUNTER.$TASK_TYPE.container false)
        SHELL=$(cat .lagoon.yml | shyaml get-value tasks.post-rollout.$COUNTER.$TASK_TYPE.shell sh)
        . /scripts/exec-post-rollout-tasks-run.sh
        ;;
    *)
        echo "Task Type ${TASK_TYPE} not implemented"; exit 1;

  esac

  let COUNTER=COUNTER+1
done
<file_sep>const R = require('ramda');
const {
  ifNotAdmin,
  inClause,
  inClauseOr,
  isPatchEmpty,
  knex,
  prepare,
  query,
  whereAnd,
} = require('./utils');
const { validateSshKey } = require('@lagoon/commons/src/jwt');

const fullSshKey = ({keyType, keyValue}) => `${keyType} ${keyValue}`;

const Sql = {
  updateSshKey: (cred, input) => {
    const { id, patch } = input;

    return knex('ssh_key')
      .where('id', '=', id)
      .update(patch)
      .toString();
  },
  selectSshKey: id =>
    knex('ssh_key')
      .where('id', '=', id)
      .toString(),
};

const getCustomerSshKeys = sqlClient => async cred => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized');
  }

  const rows = await query(
    sqlClient,
    `SELECT CONCAT(sk.keyType, ' ', sk.keyValue) as sshKey
       FROM ssh_key sk, customer c, customer_ssh_key csk
       WHERE csk.cid = c.id AND csk.skid = sk.id`,
  );

  return R.map(R.prop('sshKey'), rows);
};

const getSshKeysByProjectId = sqlClient => async (cred, pid) => {
  const { customers, projects } = cred.permissions;
  const prep = prepare(
    sqlClient,
    `SELECT
      sk.id,
      sk.name,
      sk.keyValue,
      sk.keyType,
      sk.created
    FROM project_ssh_key ps
    JOIN ssh_key sk ON ps.skid = sk.id
    JOIN project p ON ps.pid = p.id
    WHERE ps.pid = :pid
      ${ifNotAdmin(
        cred.role,
        `AND (${inClauseOr([['p.customer', customers], ['p.id', projects]])})`,
      )}
    `,
  );

  const rows = await query(sqlClient, prep({ pid }));

  return rows ? rows : null;
};

const getSshKeysByCustomerId = sqlClient => async (cred, cid) => {
  const { customers } = cred.permissions;

  const prep = sqlClient.prepare(`
      SELECT
        id,
        name,
        keyValue,
        keyType,
        created
      FROM customer_ssh_key cs
      JOIN ssh_key sk ON cs.skid = sk.id
      WHERE cs.cid = :cid
      ${ifNotAdmin(cred.role, `AND ${inClause('cs.cid', customers)}`)}
    `);

  const rows = await query(sqlClient, prep({ cid }));

  return rows;
};

const deleteSshKey = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized');
  }

  const prep = prepare(sqlClient, 'CALL DeleteSshKey(:name)');
  const rows = await query(sqlClient, prep(input));

  //TODO: maybe check rows for any changed rows?
  return 'success';
};

const addSshKey = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('Project creation unauthorized.');
  }

  if (!validateSshKey(fullSshKey(input))) {
    throw new Error('Invalid SSH key format! Please verify keyType + keyValue');
  }

  const prep = prepare(
    sqlClient,
    `CALL CreateSshKey(
        :id,
        :name,
        :keyValue,
        :keyType
      );
    `,
  );
  const rows = await query(sqlClient, prep(input));

  const ssh_key = R.path([0, 0], rows);
  return ssh_key;
};

const addSshKeyToProject = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized.');
  }

  const prep = prepare(
    sqlClient,
    'CALL CreateProjectSshKey(:project, :sshKey)',
  );
  const rows = await query(sqlClient, prep(input));
  const project = R.path([0, 0], rows);

  return project;
};

const removeSshKeyFromProject = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized.');
  }

  const prep = prepare(
    sqlClient,
    'CALL DeleteProjectSshKey(:project, :sshKey)',
  );
  const rows = await query(sqlClient, prep(input));
  const project = R.path([0, 0], rows);

  return project;
};

const addSshKeyToCustomer = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('unauthorized.');
  }

  const prep = prepare(
    sqlClient,
    'CALL CreateCustomerSshKey(:customer, :sshKey)',
  );
  const rows = await query(sqlClient, prep(input));
  const customer = R.path([0, 0], rows);

  return customer;
};

const removeSshKeyFromCustomer = sqlClient => async (cred, input) => {
  if (cred.role !== 'admin') {
    throw new Error('Unauthorized.');
  }

  const prep = prepare(
    sqlClient,
    'CALL DeleteCustomerSshKey(:customer, :sshKey)',
  );
  const rows = await query(sqlClient, prep(input));
  const customer = R.path([0, 0], rows);

  return customer;
};

const updateSshKey = sqlClient => async (cred, input) => {
  const sshKeyId = R.path(['permissions', 'sshKeyId'], cred);
  const skid = input.id.toString();

  if (cred.role !== 'admin' && !R.equals(sshKeyId, skid)) {
    throw new Error('Unauthorized.');
  }

  if (isPatchEmpty(input)) {
    throw new Error('input.patch requires at least 1 attribute');
  }

  if (!validateSshKey(fullSshKey(input.patch))) {
    throw new Error('Invalid SSH key format! Please verify keyType + keyValue');
  }

  await query(sqlClient, Sql.updateSshKey(cred, input));
  const rows = await query(sqlClient, Sql.selectSshKey(skid));

  return R.prop(0, rows);
};

const Queries = {
  addSshKey,
  addSshKeyToCustomer,
  addSshKeyToProject,
  deleteSshKey,
  getCustomerSshKeys,
  getSshKeysByCustomerId,
  getSshKeysByProjectId,
  removeSshKeyFromCustomer,
  removeSshKeyFromProject,
  updateSshKey,
};

module.exports = {
  Sql,
  Queries,
};
<file_sep>#!/bin/bash

if [ -n "$ROUTER_URL" ]; then
  SERVICE_ROUTER_URL=${SERVICE_NAME}.${ROUTER_URL}
else
  SERVICE_ROUTER_URL=""
fi

JSON=$(oc process --insecure-skip-tls-verify \
  -n ${OPENSHIFT_PROJECT} \
  -f ${OPENSHIFT_TEMPLATE} \
  -p SERVICE_NAME="${SERVICE_NAME}" \
  -p SAFE_BRANCH="${SAFE_BRANCH}" \
  -p SAFE_PROJECT="${SAFE_PROJECT}" \
  -p BRANCH="${BRANCH}" \
  -p PROJECT="${PROJECT}" \
  -p LAGOON_GIT_SHA="${LAGOON_GIT_SHA}" \
  -p SERVICE_ROUTER_URL="${SERVICE_ROUTER_URL}" \
  -p REGISTRY="${OPENSHIFT_REGISTRY}" \
  -p OPENSHIFT_PROJECT=${OPENSHIFT_PROJECT} \
  "${TEMPLATE_PARAMETERS[@]}")

# If the deploymentconfig already exists, remove `image` from all DeploymentConfig Container definition
# As setting this causes OpenShift => 3.7 to think the image has changed even though there is an ImageTrigger
if oc --insecure-skip-tls-verify -n ${OPENSHIFT_PROJECT} get dc "$SERVICE_NAME" &> /dev/null; then
  echo "$JSON" | jq --raw-output 'del(.items[].spec.template.spec.containers[]?.image)' | oc apply --insecure-skip-tls-verify -n ${OPENSHIFT_PROJECT} -f -
else
  echo "$JSON" | oc apply --insecure-skip-tls-verify -n ${OPENSHIFT_PROJECT} -f -
fi