diff --git a/includes/cache.yml b/includes/cache.yml
new file mode 100644
index 0000000000000000000000000000000000000000..434f372cbde4c77caddf41b83bb03342793dada3
--- /dev/null
+++ b/includes/cache.yml
@@ -0,0 +1,8 @@
+.cache-node-modules:
+  cache:
+    key: "$CI_COMMIT_REF_SLUG" # we have a fallback configured using CACHE_FALLBACK_KEY
+    policy: pull-push
+    paths:
+      - "$APP_DIR/node_modules/"
+      - "$APP_DIR/.next/cache/"
+      - "$CYPRESS_CACHE_FOLDER"
diff --git a/includes/docker-build.yml b/includes/docker-build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..89e74cbbb1b1447f37433af48f3e720843b3de17
--- /dev/null
+++ b/includes/docker-build.yml
@@ -0,0 +1,93 @@
+.ensureStorybookDockerfile: &ensureStorybookDockerfile |
+  function ensureStorybookDockerfile() {
+    # this is for meteor
+    if [ ! -f Dockerfile-storybook ]; then
+      echo "Creating Dockerfile-storybook"
+      cat > Dockerfile-storybook <<EOF
+  FROM nginx
+  EXPOSE 80
+  COPY .storybook-out/ /usr/share/nginx/html
+  EOF
+
+    fi
+
+  }
+
+.docker-build-base:
+  image: docker:18-dind
+  retry: 2
+  services:
+    - docker:18-dind
+  variables:
+    DOCKER_DRIVER: overlay2
+
+.docker-build-app-base:
+  extends: .docker-build-base
+  script:
+    - docker login --username gitlab-ci-token --password $CI_JOB_TOKEN $CI_REGISTRY
+
+    - docker pull $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG || true
+    - docker build --cache-from $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG --tag $CI_REGISTRY_IMAGE:$IMAGE_TAG $DOCKER_DIR
+    - docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG
+    - docker tag $CI_REGISTRY_IMAGE:$IMAGE_TAG $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG
+    - docker push $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG
+
+.docker-build-storybook-base:
+  extends: .docker-build-base
+  script:
+    - *ensureStorybookDockerfile
+    - ensureStorybookDockerfile
+    - docker login --username gitlab-ci-token --password $CI_JOB_TOKEN $CI_REGISTRY
+    - docker pull $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook || true
+    - docker build --cache-from $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook --tag $CI_REGISTRY_IMAGE:$IMAGE_TAG-storybook $DOCKER_DIR -f Dockerfile-storybook
+    - docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG-storybook
+    - docker tag $CI_REGISTRY_IMAGE:$IMAGE_TAG-storybook $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook
+    - docker push $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook
+
+.docker-build:
+  stage: docker-build
+  extends:
+    - .rules-always
+    - .docker-build-app-base
+  needs: ["app-build"]
+
+.docker-build-storybook:
+  stage: docker-build
+  only:
+    refs:
+      - master
+      - main
+      - production
+      - tags
+      - merge_requests
+    variables:
+      - $STORYBOOK == "true"
+  extends: .docker-build-storybook-base
+  needs: ["storybook-build"]
+
+.storybook-build:
+  extends:
+    - .cache-node-modules
+  cache:
+    policy: pull
+  stage: build
+  only:
+    refs:
+      - master
+      - main
+      - production
+      - tags
+      - merge_requests
+    variables:
+      - $STORYBOOK == "true"
+
+  needs: ["setup-build-info"]
+  # don't do it after the semantic release commit, but do it after tag
+  script:
+    - cd $APP_PATH
+    - if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi
+    - yarn install --frozen-lockfile
+    - yarn build-storybook -c .storybook -s public -o .storybook-out --quiet
+  artifacts:
+    paths:
+      - $CI_PROJECT_DIR/.storybook-out
diff --git a/includes/env.yml b/includes/env.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a01aa0d5753845e6af6d6c7bc23e5e8c61fdad67
--- /dev/null
+++ b/includes/env.yml
@@ -0,0 +1,50 @@
+.env-base:
+  variables:
+    # deprecated, use KUBE_NAMESPACE
+    NAMESPACE: "${CUSTOMER_NAME}-${APP_NAME}-${ENV_SHORT}"
+
+    HOST_CANONICAL: $APP_NAME-$CI_ENVIRONMENT_SLUG.$CUSTOMER_NAME.panter.cloud
+    IMAGE_PULL_SECRET: gitlab-registry-$COMPONENT_NAME
+  environment:
+    url: https://$APP_NAME-$CI_ENVIRONMENT_SLUG.$CUSTOMER_NAME.panter.cloud
+    name: ${ENV_SHORT}-${COMPONENT_NAME}
+    kubernetes:
+      namespace: "${CUSTOMER_NAME}-${APP_NAME}-${ENV_SHORT}"
+
+.env-review:
+  extends: .env-base
+  variables:
+    ENV_SHORT: review
+    REPLICAS: 1
+    MONGODB_REPLICAS: 1
+    MONGODB_STORAGE_CLASS: "standard"
+    MONGODB_BACKUP_ENABLED: "false"
+    DEPLOY_SSL_ISSUER: letsencrypt-staging
+    KUBE_APP_NAME: ${COMPONENT_NAME}-${CI_COMMIT_REF_SLUG}
+
+  environment:
+    name: ${ENV_SHORT}-${COMPONENT_NAME}/$CI_COMMIT_REF_NAME
+    on_stop: review-stop
+    auto_stop_in: 2 weeks
+
+.env-dev:
+  extends: .env-base
+  variables:
+    REPLICAS: 1
+    MONGODB_REPLICAS: 1
+    MONGODB_STORAGE_CLASS: "standard"
+    DEPLOY_SSL_ISSUER: letsencrypt-staging
+    ENV_SHORT: dev
+
+.env-stage:
+  extends: .env-base
+  variables:
+    REPLICAS: 2
+    DEPLOY_SSL_ISSUER: letsencrypt-staging
+    ENV_SHORT: stage
+
+.env-prod:
+  extends: .env-base
+  variables:
+    STORYBOOK: "false" # don't deploy storybook to prod, never
+    ENV_SHORT: prod
diff --git a/includes/kubernetes.yml b/includes/kubernetes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e66fb41a16904d180c099a6d2a50394b347e690
--- /dev/null
+++ b/includes/kubernetes.yml
@@ -0,0 +1,128 @@
+.auto_devops: &auto_devops |
+
+  function kubernetesEnsureNamespace() {
+    echo "Ensure Namespace $NAMESPACE"
+    kubectl describe namespace "$NAMESPACE" || kubectl create namespace "$NAMESPACE"
+  }
+
+  function kubernetesCreateSecret() {
+    kubectl create secret -n "$NAMESPACE" \
+    docker-registry $IMAGE_PULL_SECRET \
+      --docker-server="$CI_REGISTRY" \
+      --docker-username="${CI_DEPLOY_USER:-$CI_REGISTRY_USER}" \
+      --docker-password="${CI_DEPLOY_PASSWORD:-$CI_REGISTRY_PASSWORD}" \
+      --docker-email="$GITLAB_USER_EMAIL" \
+      -o yaml --dry-run | kubectl replace -n "$NAMESPACE" --force -f -
+  }
+
+  function kubernetesDeploy() {
+    
+    echo "Deploy to kubernetes"
+    RELEASE_NAME=${CUSTOMER_NAME}-${APP_NAME}-${CI_ENVIRONMENT_SLUG}
+    echo "Release: $RELEASE_NAME"
+    echo "URL (canonical): $HOST_CANONICAL"
+    helm init --client-only --stable-repo-url=https://charts.helm.sh/stable
+    helm repo add panter-charts http://panter.git.panter.biz/helm-charts
+    helm repo update
+    echo "panter chart repo added"
+
+    # use helmArgs defined in variables
+    helmArgsEvaluated=$(echo $helmArgs | envsubst )
+
+    # this bash-brainfuck simply splits $helmArgs by newline to an array...
+    readarray -t helmArgsArray <<<"$helmArgsEvaluated"
+
+    #Log the time until selfdestruct in case of review environment
+    if [[ ${ENV_SHORT} == "review" ]]; then   echo "NOTE - This deployment will stop itself after 2 weeks"; fi
+
+    # find all values files and generate an array out of it
+    files=(values.yml values-$ENV_SHORT.yml values-$CI_ENVIRONMENT_SLUG.yml)
+    values=()
+    for i in "${files[@]}"; do if [  -f $VALUES_PATH/$i ]; then values+=(--values $VALUES_PATH/$i); fi; done
+    echo "reading values files from $VALUES_PATH. found ${values[@]}"
+
+    echo "doing helm upgrade"
+    helm upgrade --install "$RELEASE_NAME" $CHART_NAME \
+      --wait \
+      --timeout 600 \
+      --cleanup-on-fail \
+      --set global.componentName="$KUBE_APP_NAME" \
+      --set global.ENV_SHORT="$ENV_SHORT" \
+      --set image.repository="$CI_REGISTRY_IMAGE" \
+      --set image.pullSecret="$IMAGE_PULL_SECRET" \
+      --set image.tag="$IMAGE_TAG" \
+      --set image.pullPolicy=IfNotPresent \
+      --set storybook.enabled=$STORYBOOK \
+      --set application.replicas="$REPLICAS" \
+      --set application.hostCanonical="$HOST_CANONICAL" \
+      --set application.worker.enabled=$WORKER_ENABLED \
+      --set mongodb.enabled=$MONGODB_ENABLED \
+      --set mongodb.backup.enabled=$MONGODB_BACKUP_ENABLED \
+      --set mongodb-replicaset.replicas=$MONGODB_REPLICAS \
+      --set mongodb-replicaset.fullnameOverride=$KUBE_APP_NAME-mongodb-replicaset \
+      --set mongodb-replicaset.persistentVolume.storageClass="$MONGODB_STORAGE_CLASS" \
+      --set prisma.enabled=$PRISMA_ENABLED \
+      --set gitlab.visualReviewEnabled="$GITLAB_VISUAL_REVIEW_ENABLED" \
+      --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
+      --set gitlab.projectPath="$CI_PROJECT_PATH" \
+      --set gitlab.mergeRequestId="$CI_MERGE_REQUEST_IID" \
+      --set gitlab.projectId="$CI_PROJECT_ID" \
+      --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
+      --set namespace="$NAMESPACE" \
+      --namespace="$NAMESPACE" \
+      ${helmArgsArray[@]} \
+      ${values[@]}
+      echo "Deployment successful 😻"
+  }
+
+  function kubernetesDelete() {
+    RELEASE_NAME=${CUSTOMER_NAME}-${APP_NAME}-${CI_ENVIRONMENT_SLUG}
+    echo "Delete $RELEASE_NAME"
+    name="$RELEASE_NAME"
+
+    if [[ -n "$(helm ls -q "^$name$")" ]]; then
+      helm delete --purge "$name"
+     
+    fi
+  }
+
+.deploy-to-kubernetes:
+  extends: .env-base
+  retry: 2
+  script:
+    - *auto_devops
+    - kubernetesEnsureNamespace
+    - kubernetesCreateSecret
+    - kubernetesDeploy
+
+.deploy-to-kubernetes-review:
+  extends:
+    - .deploy-to-kubernetes
+    - .env-review
+
+.kubernetes-review-stop:
+  variables:
+    KUBE_APP_NAME: ${COMPONENT_NAME}-${CI_COMMIT_REF_SLUG}
+    ENV_SHORT: review
+    GIT_STRATEGY: none
+  environment:
+    name: ${ENV_SHORT}-${COMPONENT_NAME}/$CI_COMMIT_REF_NAME
+    action: stop
+  script:
+    - *auto_devops
+    - kubernetesDelete
+
+.deploy-to-kubernetes-dev:
+  extends:
+    - .deploy-to-kubernetes
+    - .env-dev
+
+.deploy-to-kubernetes-stage:
+  extends:
+    - .deploy-to-kubernetes
+    - .env-stage
+
+.deploy-to-kubernetes-prod:
+  extends:
+    - .deploy-to-kubernetes
+    - .env-prod
diff --git a/includes/open-mr.yml b/includes/open-mr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..495a1c7abc0b8894b2e18a035ad9b722423aa93c
--- /dev/null
+++ b/includes/open-mr.yml
@@ -0,0 +1,14 @@
+.openMr:
+  image: tmaier/gitlab-auto-merge-request:1
+  before_script: [] # We do not need any setup work, let's remove the global one (if any)
+  variables:
+    GIT_STRATEGY: none # We do not need a clone of the GIT repository to create a Merge Request
+  stage: openMr
+  script:
+    - export GITLAB_PRIVATE_TOKEN=$GL_TOKEN
+    - merge-request.sh # The name of the script
+  only:
+    refs:
+      - branches
+    variables:
+      - $AUTOMATIC_MR == "true"
diff --git a/includes/rules.yml b/includes/rules.yml
new file mode 100644
index 0000000000000000000000000000000000000000..80f36c2a2be0154841f876d4b94ca3bd754d9a47
--- /dev/null
+++ b/includes/rules.yml
@@ -0,0 +1,45 @@
+.rules-always:
+  rules:
+    - if: $CI_COMMIT_TAG
+    - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\).*/ # not after semantic release commit
+      when: never
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+    - if: $CI_COMMIT_BRANCH =~ /^[0-9]+\.([0-9]+|x)\.x$/ # hotfix branches
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+
+.rules-always-but-not-on-tags:
+  rules:
+    - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\).*/ # not after semantic release commit
+      when: never
+    - if: $CI_COMMIT_TAG
+      when: never
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+    - if: $CI_COMMIT_BRANCH =~ /^[0-9]+\.([0-9]+|x)\.x$/ # hotfix branches
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+
+.rules-dev-deploy:
+  rules:
+    - if: $CI_COMMIT_MESSAGE =~ /^chore.*/ # not after semaitc release commit
+      when: never
+    - if: $CI_COMMIT_MESSAGE =~ /^chore\(maintenance\).*/ # not aftter hotfix release
+      when: never
+    - if: $CI_COMMIT_TAG # after releases
+      when: on_success
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+      when: on_success
+
+.rules-stage-deploy:
+  rules:
+    - if: $CI_COMMIT_TAG && $CI_COMMIT_MESSAGE =~ /^chore\(maintenance\).*/ # d
+      when: manual # do not do automatic release after a hotfix / maintenance release
+    - if: $CI_COMMIT_TAG && $STAGING_ENABLED == "true"
+      when: on_success
+
+.rules-prod-deploy:
+  rules:
+    - if: $CI_COMMIT_TAG && $CI_COMMIT_MESSAGE =~ /^chore\(maintenance\).*/ # d
+      when: manual # do not do automatic release after a hotfix / maintenance release
+    - if: $CI_COMMIT_TAG && $STAGING_ENABLED != "true" # do only automatic release when staging is not enabled
+      when: on_success
+    - if: $CI_COMMIT_TAG
+      when: manual
diff --git a/includes/semantic-release.yml b/includes/semantic-release.yml
new file mode 100644
index 0000000000000000000000000000000000000000..014b3026727c5c810709f11945596a75e6afa19f
--- /dev/null
+++ b/includes/semantic-release.yml
@@ -0,0 +1,90 @@
+.semanticRelease-script: &semanticRelease-script |
+
+  function semanticRelease() {
+    # 1. create "@semantic-release/changelog"
+    # 2. commit it to git (and push)
+    # 3. optional: update package.json
+
+    # better branches settings: https://github.com/semantic-release/semantic-release/issues/1581
+   
+    echo "doing semantic release"
+
+    yarn global add --silent semantic-release \
+                             @semantic-release/commit-analyzer \
+                             @semantic-release/release-notes-generator \
+                             https://github.com/panter/git.git#b4037626cc583d3e9f7995ea0604b23730d0a22e \
+                             @semantic-release/changelog \
+                             @semantic-release/gitlab \
+                             @semantic-release/exec;
+
+    if [ ! -e .releaserc ]; then
+    cat > .releaserc <<EOF
+    {
+      "plugins": [
+         [
+          "@semantic-release/commit-analyzer",
+          {
+            preset: "angular",
+            releaseRules: [
+              { breaking: true, release: "major" },
+              { revert: true, release: "patch" },
+
+              { type: "feat", release: "minor" },
+              { type: "docs", release: "patch" },
+              { type: "fix", release: "patch" },
+              { type: "perf", release: "patch" },
+              # always fall back to a patch release
+              {
+                message: "*",
+                release: "patch",
+              },
+            ],
+          },
+        ],
+        "@semantic-release/release-notes-generator",
+        "@semantic-release/changelog",
+        ["@semantic-release/git", {
+          "message": "chore(\${branch.type}): \${nextRelease.version}\n\n\${nextRelease.notes}"
+        }],
+        ["@semantic-release/gitlab", {
+          "gitlabUrl": "https://git.panter.ch"
+        }]
+        ],
+      "branches": [
+        "+([0-9])?(.{+([0-9]),x}).x",
+        "master",
+        "main",
+        "next",
+        "next-major",
+        {
+          "name": "beta",
+          "prerelease": true
+        },
+        {
+          "name": "alpha",
+          "prerelease": true
+        }
+      ],
+    }
+  EOF
+    fi
+    semantic-release
+  }
+
+.semantic-release-base:
+  script:
+    - *semanticRelease-script
+    - semanticRelease
+
+.create-release:
+  stage: create-release
+  extends: .semantic-release-base
+  rules:
+    - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\).*/ # not after semantic release commit
+      when: never
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $AUTO_RELEASE == "true"
+      when: on_success
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+      when: manual
+    - if: $CI_COMMIT_BRANCH =~ /^[0-9]+\.([0-9]+|x)\.x$/ # hotfix branches
+      when: manual
diff --git a/includes/setup.yml b/includes/setup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..01d793a8b72efecd8d83959beffc3cd5ba9c63f9
--- /dev/null
+++ b/includes/setup.yml
@@ -0,0 +1,21 @@
+.setup-build-info:
+  stage: setup
+  extends:
+    - .rules-always
+  retry: 2
+  script:
+    - cd $APP_PATH
+    - BUILD_TAG="$(git describe --tags || git rev-parse HEAD)"
+    - BUILD_ID="$COMPONENT_NAME-$BUILD_TAG"
+    - BUILD_COMMIT="$(git rev-parse HEAD)"
+    - BUILD_TIME="$(date)"
+    - echo "BUILD_TAG=$BUILD_TAG" >> build.env
+    - echo "BUILD_ID=$BUILD_ID" >> build.env
+    - echo "BUILD_COMMIT=$BUILD_COMMIT" >> build.env
+    - echo "BUILD_TIME=$BUILD_TIME" >> build.env
+    - echo '{"id":"'$BUILD_ID'","commit":"'$BUILD_COMMIT'","tag":"'$BUILD_TAG'","time":"'$BUILD_TIME'"}' > __build_info.json
+  artifacts:
+    paths:
+      - $CI_PROJECT_DIR/__build_info.json
+    reports:
+      dotenv: build.env
diff --git a/meteor-kubernetes.yml b/meteor-kubernetes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f3798fb7cc4545d04ac7e06ceaf55f3145341f35
--- /dev/null
+++ b/meteor-kubernetes.yml
@@ -0,0 +1,54 @@
+include:
+  - node-kubernetes.yml
+
+app-build:
+  extends: .app-build
+  script:
+    - cd $APP_PATH
+    - echo "add healthcheck package"
+    - meteor add panter:healthroute --allow-superuser
+    - meteor add qualia:prod-shell --allow-superuser
+    - echo "current path is $(pwd)"
+    - yarn
+    - echo "building into $CI_PROJECT_DIR"
+    - TOOL_NODE_FLAGS="--max_old_space_size=3584 --min_semi_space_size=8 --max_semi_space_size=256 --optimize_for_size" meteor build $CI_PROJECT_DIR --architecture os.linux.x86_64 --allow-superuser --server-only --directory
+    - cp $CI_PROJECT_DIR/__build_info.json $CI_PROJECT_DIR/bundle/programs/server
+  artifacts:
+    paths:
+      - $CI_PROJECT_DIR/__build_info.json
+      - $CI_PROJECT_DIR/bundle
+
+.ensureDocker: &ensureDocker |
+  function ensureDockerfile() {
+    if [ ! -f .dockerignore ]; then 
+      cat > .dockerignore <<EOF
+  node_modules
+  .git
+
+  EOF
+    fi
+      
+    if [ ! -f Dockerfile ]; then
+      echo "Creating Dockerfile"
+      NODE_VERSION_FULL=$(cat bundle/.node_version.txt)
+      NODE_VERSION=${NODE_VERSION_FULL/v} # remove v
+      
+      cat > Dockerfile <<EOF
+  FROM node:$NODE_VERSION
+  ADD . /src
+  RUN cd /src/bundle/programs/server && npm install
+
+  WORKDIR /src/bundle
+  EXPOSE 8888
+  CMD ["node", "main.js"]
+  EOF
+
+    fi
+
+  }
+
+docker-build:
+  extends: .docker-build
+  before_script:
+    - *ensureDocker
+    - ensureDockerfile
diff --git a/node-kubernetes.yml b/node-kubernetes.yml
index d23d9c8d3d3e82259c34b13cccba3e4839ecfb30..7573cf19c0877c6a813cdbc2c5c763d083723cce 100644
--- a/node-kubernetes.yml
+++ b/node-kubernetes.yml
@@ -44,3 +44,52 @@ docker-build:
   before_script:
     - *ensureDocker
     - ensureDockerfile
+
+.before-script-yarn:
+  extends: .cache-node-modules
+  before_script:
+    - cd $APP_PATH
+    - if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi
+    - yarn install --frozen-lockfile
+
+.test-node:
+  stage: test
+
+  extends:
+    - .before-script-yarn
+    - .test-base
+
+  script:
+    - yarn test
+
+test:
+  extends: .test-node
+
+.lint-node:
+  stage: test
+  extends:
+    - .before-script-yarn
+    - .lint-base
+
+  script:
+    - yarn lint
+  needs: []
+
+lint:
+  extends: .lint-node
+
+.app-build-node:
+  extends:
+    - .before-script-yarn
+    - .app-build-base
+  script:
+    - yarn build
+
+  artifacts:
+    paths:
+      - $CI_PROJECT_DIR/__build_info.json
+      - $CI_PROJECT_DIR/dist
+      - $CI_PROJECT_DIR/.next
+
+app-build:
+  extends: .app-build-node
diff --git a/panter-kubernetes-base.yml b/panter-kubernetes-base.yml
index 2ec95255e861f5f81fd0ab8ef7ff40ca891354e4..c6f041c48f065cf0ef785069995335be41eb3906 100644
--- a/panter-kubernetes-base.yml
+++ b/panter-kubernetes-base.yml
@@ -1,3 +1,13 @@
+include:
+  - /includes/env.yml
+  - /includes/cache.yml
+  - /includes/rules.yml
+  - /includes/setup.yml
+  - /includes/docker-build.yml
+  - /includes/semantic-release.yml
+  - /includes/kubernetes.yml
+  - /includes/open-mr.yml
+
 variables:
   CUSTOMER_NAME: panter
   APP_NAME: demo
@@ -8,16 +18,18 @@ variables:
   WORKER_ENABLED: "false" # whether to create a custom worker
   CHART_NAME: panter-charts/the-panter-chart
   REPLICAS: "3" # number of pods (default for prod)
+
   MONGODB_ENABLED: "false"
   MONGODB_BACKUP_ENABLED: "true"
   MONGODB_REPLICAS: "3" # nubmer of mongodb replicas (default for prod)
   MONGODB_STORAGE_CLASS: "fast"
+
   DOCKER_DIR: "."
   GITLAB_VISUAL_REVIEW_ENABLED: "false"
   STORYBOOK: "false"
 
   AUTOMATIC_MR: "false"
-  # PRISMA
+  # PRISMA 1 (deprecated)
   PRISMA_ENABLED: "false"
 
   # INTERNAL VARIABLES, don't override
@@ -27,6 +39,10 @@ variables:
   # APP NAME LOCAL TO THE APP NAMESPACEGIT S
   KUBE_APP_NAME: ${COMPONENT_NAME}
 
+  # see https://on.cypress.io/caching
+  CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/.cache/Cypress"
+  CACHE_FALLBACK_KEY: $CI_DEFAULT_BRANCH
+
   # Docker
   DOCKER_HOST: tcp://0.0.0.0:2375
   DOCKER_TLS_CERTDIR: ""
@@ -45,464 +61,42 @@ image: panterch/docker-ci-kubernetes-deploy
 # SCRIPTS
 #----------------------
 
-.auto_devops: &auto_devops |
-
-  function semanticRelease() {
-    # 1. create "@semantic-release/changelog"
-    # 2. commit it to git (and push)
-    # 3. optional: update package.json
-
-    # better branches settings: https://github.com/semantic-release/semantic-release/issues/1581
-   
-    echo "doing semantic release"
-
-    yarn global add --silent semantic-release \
-                             @semantic-release/commit-analyzer \
-                             @semantic-release/release-notes-generator \
-                             https://github.com/panter/git.git#b4037626cc583d3e9f7995ea0604b23730d0a22e \
-                             @semantic-release/changelog \
-                             @semantic-release/gitlab \
-                             @semantic-release/exec;
-
-    if [ ! -e .releaserc ]; then
-    cat > .releaserc <<EOF
-    {
-      "plugins": [
-         [
-          "@semantic-release/commit-analyzer",
-          {
-            preset: "angular",
-            releaseRules: [
-              { breaking: true, release: "major" },
-              { revert: true, release: "patch" },
-
-              { type: "feat", release: "minor" },
-              { type: "docs", release: "patch" },
-              { type: "fix", release: "patch" },
-              { type: "perf", release: "patch" },
-              # always fall back to a patch release
-              {
-                message: "*",
-                release: "patch",
-              },
-            ],
-          },
-        ],
-        "@semantic-release/release-notes-generator",
-        "@semantic-release/changelog",
-        ["@semantic-release/git", {
-          "message": "chore(\${branch.type}): \${nextRelease.version}\n\n\${nextRelease.notes}"
-        }],
-        ["@semantic-release/gitlab", {
-          "gitlabUrl": "https://git.panter.ch"
-        }]
-        ],
-      "branches": [
-        "+([0-9])?(.{+([0-9]),x}).x",
-        "master",
-        "main",
-        "next",
-        "next-major",
-        {
-          "name": "beta",
-          "prerelease": true
-        },
-        {
-          "name": "alpha",
-          "prerelease": true
-        }
-      ],
-    }
-  EOF
-    fi
-    semantic-release
-  }
-
-
-  function kubernetesEnsureNamespace() {
-    echo "Ensure Namespace $NAMESPACE"
-    kubectl describe namespace "$NAMESPACE" || kubectl create namespace "$NAMESPACE"
-  }
-
-  function kubernetesCreateSecret() {
-    kubectl create secret -n "$NAMESPACE" \
-    docker-registry $IMAGE_PULL_SECRET \
-      --docker-server="$CI_REGISTRY" \
-      --docker-username="${CI_DEPLOY_USER:-$CI_REGISTRY_USER}" \
-      --docker-password="${CI_DEPLOY_PASSWORD:-$CI_REGISTRY_PASSWORD}" \
-      --docker-email="$GITLAB_USER_EMAIL" \
-      -o yaml --dry-run | kubectl replace -n "$NAMESPACE" --force -f -
-  }
-
-  function kubernetesDeploy() {
-    
-    echo "Deploy to kubernetes"
-    RELEASE_NAME=${CUSTOMER_NAME}-${APP_NAME}-${CI_ENVIRONMENT_SLUG}
-    echo "Release: $RELEASE_NAME"
-    echo "URL (canonical): $HOST_CANONICAL"
-    helm init --client-only --stable-repo-url=https://charts.helm.sh/stable
-    helm repo add panter-charts http://panter.git.panter.biz/helm-charts
-    helm repo update
-    echo "panter chart repo added"
-
-    # use helmArgs defined in variables
-    helmArgsEvaluated=$(echo $helmArgs | envsubst )
-
-    # this bash-brainfuck simply splits $helmArgs by newline to an array...
-    readarray -t helmArgsArray <<<"$helmArgsEvaluated"
-
-    #Log the time until selfdestruct in case of review environment
-    if [[ ${ENV_SHORT} == "review" ]]; then   echo "NOTE - This deployment will stop itself after 2 weeks"; fi
-
-    # find all values files and generate an array out of it
-    files=(values.yml values-$ENV_SHORT.yml values-$CI_ENVIRONMENT_SLUG.yml)
-    values=()
-    for i in "${files[@]}"; do if [  -f $VALUES_PATH/$i ]; then values+=(--values $VALUES_PATH/$i); fi; done
-    echo "reading values files from $VALUES_PATH. found ${values[@]}"
-
-    echo "doing helm upgrade"
-    helm upgrade --install "$RELEASE_NAME" $CHART_NAME \
-      --wait \
-      --timeout 600 \
-      --cleanup-on-fail \
-      --set global.componentName="$KUBE_APP_NAME" \
-      --set global.ENV_SHORT="$ENV_SHORT" \
-      --set image.repository="$CI_REGISTRY_IMAGE" \
-      --set image.pullSecret="$IMAGE_PULL_SECRET" \
-      --set image.tag="$IMAGE_TAG" \
-      --set image.pullPolicy=IfNotPresent \
-      --set storybook.enabled=$STORYBOOK \
-      --set application.replicas="$REPLICAS" \
-      --set application.hostCanonical="$HOST_CANONICAL" \
-      --set application.worker.enabled=$WORKER_ENABLED \
-      --set mongodb.enabled=$MONGODB_ENABLED \
-      --set mongodb.backup.enabled=$MONGODB_BACKUP_ENABLED \
-      --set mongodb-replicaset.replicas=$MONGODB_REPLICAS \
-      --set mongodb-replicaset.fullnameOverride=$KUBE_APP_NAME-mongodb-replicaset \
-      --set mongodb-replicaset.persistentVolume.storageClass="$MONGODB_STORAGE_CLASS" \
-      --set prisma.enabled=$PRISMA_ENABLED \
-      --set gitlab.visualReviewEnabled="$GITLAB_VISUAL_REVIEW_ENABLED" \
-      --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
-      --set gitlab.projectPath="$CI_PROJECT_PATH" \
-      --set gitlab.mergeRequestId="$CI_MERGE_REQUEST_IID" \
-      --set gitlab.projectId="$CI_PROJECT_ID" \
-      --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
-      --set namespace="$NAMESPACE" \
-      --namespace="$NAMESPACE" \
-      ${helmArgsArray[@]} \
-      ${values[@]}
-      echo "Deployment successful 😻"
-  }
-
-  function kubernetesDelete() {
-    RELEASE_NAME=${CUSTOMER_NAME}-${APP_NAME}-${CI_ENVIRONMENT_SLUG}
-    echo "Delete $RELEASE_NAME"
-    name="$RELEASE_NAME"
-
-    if [[ -n "$(helm ls -q "^$name$")" ]]; then
-      helm delete --purge "$name"
-     
-    fi
-  }
-
-.ensureStorybookDockerfile: &ensureStorybookDockerfile |
-  function ensureStorybookDockerfile() {
-    # this is for meteor
-    if [ ! -f Dockerfile-storybook ]; then
-      echo "Creating Dockerfile-storybook"
-      cat > Dockerfile-storybook <<EOF
-  FROM nginx
-  EXPOSE 80
-  COPY .storybook-out/ /usr/share/nginx/html
-  EOF
-
-    fi
-
-  }
-
-### jobs
-
-.openMr:
-  image: tmaier/gitlab-auto-merge-request:1
-  before_script: [] # We do not need any setup work, let's remove the global one (if any)
-  variables:
-    GIT_STRATEGY: none # We do not need a clone of the GIT repository to create a Merge Request
-  stage: openMr
-  script:
-    - export GITLAB_PRIVATE_TOKEN=$GL_TOKEN
-    - merge-request.sh # The name of the script
-  only:
-    refs:
-      - branches
-    variables:
-      - $AUTOMATIC_MR == "true"
-
-.cache-node-modules: &cache-node-modules
-  key: "node-modules"
-  policy: pull-push
-  paths:
-    - "$APP_DIR/node_modules/"
-    - "$APP_DIR/.next/cache/"
-
-.docker-build-base:
-  image: docker:18-dind
-  retry: 2
-  services:
-    - docker:18-dind
-  variables:
-    DOCKER_DRIVER: overlay2
-
-.docker-build-app-base:
-  extends: .docker-build-base
-  script:
-    - docker login --username gitlab-ci-token --password $CI_JOB_TOKEN $CI_REGISTRY
-
-    - docker pull $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG || true
-    - docker build --cache-from $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG --tag $CI_REGISTRY_IMAGE:$IMAGE_TAG $DOCKER_DIR
-    - docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG
-    - docker tag $CI_REGISTRY_IMAGE:$IMAGE_TAG $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG
-    - docker push $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG
-.docker-build-storybook-base:
-  extends: .docker-build-base
-  script:
-    - *ensureStorybookDockerfile
-    - ensureStorybookDockerfile
-    - docker login --username gitlab-ci-token --password $CI_JOB_TOKEN $CI_REGISTRY
-    - docker pull $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook || true
-    - docker build --cache-from $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook --tag $CI_REGISTRY_IMAGE:$IMAGE_TAG-storybook $DOCKER_DIR -f Dockerfile-storybook
-    - docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG-storybook
-    - docker tag $CI_REGISTRY_IMAGE:$IMAGE_TAG-storybook $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook
-    - docker push $CI_REGISTRY_IMAGE:$CACHE_IMAGE_TAG-storybook
-
-.defaultEnv:
-  variables:
-    NAMESPACE: "${CUSTOMER_NAME}-${APP_NAME}-${ENV_SHORT}"
-    HOST_CANONICAL: $APP_NAME-$CI_ENVIRONMENT_SLUG.$CUSTOMER_NAME.panter.cloud
-    IMAGE_PULL_SECRET: gitlab-registry-$COMPONENT_NAME
-  environment:
-    url: https://$APP_NAME-$CI_ENVIRONMENT_SLUG.$CUSTOMER_NAME.panter.cloud
-    name: ${ENV_SHORT}-${COMPONENT_NAME}
-
-.semantic-release-base:
-  cache:
-    <<: *cache-node-modules
-  script:
-    - *auto_devops
-    - semanticRelease
-
-.deploy-to-kubernetes:
-  extends: .defaultEnv
-  retry: 2
-  script:
-    - *auto_devops
-    - kubernetesEnsureNamespace
-    - kubernetesCreateSecret
-    - kubernetesDeploy
-
-.deploy-to-kubernetes-review:
-  extends: .deploy-to-kubernetes
-  variables:
-    ENV_SHORT: review
-    REPLICAS: 1
-    MONGODB_REPLICAS: 1
-    MONGODB_STORAGE_CLASS: "standard"
-    MONGODB_BACKUP_ENABLED: "false"
-    DEPLOY_SSL_ISSUER: letsencrypt-staging
-    KUBE_APP_NAME: ${COMPONENT_NAME}-${CI_COMMIT_REF_SLUG}
-
-  environment:
-    name: ${ENV_SHORT}-${COMPONENT_NAME}/$CI_COMMIT_REF_NAME
-    on_stop: review-stop
-    auto_stop_in: 2 weeks
-
-.kubernetes-review-stop:
-  when: manual
-  variables:
-    KUBE_APP_NAME: ${COMPONENT_NAME}-${CI_COMMIT_REF_SLUG}
-    ENV_SHORT: review
-    GIT_STRATEGY: none
-  environment:
-    name: ${ENV_SHORT}-${COMPONENT_NAME}/$CI_COMMIT_REF_NAME
-    action: stop
-  script:
-    - *auto_devops
-    - kubernetesDelete
-  cache: {}
-
-.deploy-to-kubernetes-dev:
-  extends: .deploy-to-kubernetes
-  variables:
-    REPLICAS: 1
-    MONGODB_REPLICAS: 1
-    MONGODB_STORAGE_CLASS: "standard"
-    DEPLOY_SSL_ISSUER: letsencrypt-staging
-    ENV_SHORT: dev
-
-.deploy-to-kubernetes-stage:
-  extends: .deploy-to-kubernetes
-
-  variables:
-    REPLICAS: 2
-    DEPLOY_SSL_ISSUER: letsencrypt-staging
-    ENV_SHORT: stage
-
-.deploy-to-kubernetes-prod:
-  extends: .deploy-to-kubernetes
-  variables:
-    STORYBOOK: "false" # don't deploy storybook to prod, never
-    ENV_SHORT: prod
-
-# ----------------------
-# RESTRICTION INCLUDES
-# ----------------------
-.only-default: &only-default
-  only:
-    refs:
-      - master
-      - main
-      - production
-      - tags
-      - merge_requests
-      - /^[0-9]+\.([0-9]+|x)\.x$/ # hotfix branches
-.only-storybook: &only-storybook
-  only:
-    refs:
-      - master
-      - main
-      - production
-      - tags
-      - merge_requests
-    variables:
-      - $STORYBOOK == "true"
-
-.only-on-merge-request: &only-on-merge-request
-  only:
-    refs:
-      - merge_requests
-      - /^[0-9]+\.([0-9]+|x)\.x$/ # hotfix branches
-
-.not-after-semantic-release: &not-after-semantic-release
-  except:
-    refs:
-      - tags
-    variables:
-      - $CI_COMMIT_MESSAGE =~ /^chore.*/
-
-.only-on-master: &only-on-master
-  only:
-    refs:
-      - master
-      - main
-
-.not-on-master: &not-on-master
-  except:
-    refs:
-      - master
-      - main
-
 #----------------------
 # DEFAULT STAGE TEMPLATES
 #----------------------
 
-.setup-build-info:
-  stage: setup
-  <<: *only-default
-  retry: 2
-  script:
-    - cd $APP_PATH
-    - BUILD_TAG="$(git describe --tags || git rev-parse HEAD)"
-    - BUILD_ID="$COMPONENT_NAME-$BUILD_TAG"
-    - BUILD_COMMIT="$(git rev-parse HEAD)"
-    - BUILD_TIME="$(date)"
-    - echo "BUILD_TAG=$BUILD_TAG" >> build.env
-    - echo "BUILD_ID=$BUILD_ID" >> build.env
-    - echo "BUILD_COMMIT=$BUILD_COMMIT" >> build.env
-    - echo "BUILD_TIME=$BUILD_TIME" >> build.env
-    - echo '{"id":"'$BUILD_ID'","commit":"'$BUILD_COMMIT'","tag":"'$BUILD_TAG'","time":"'$BUILD_TIME'"}' > __build_info.json
-  artifacts:
-    paths:
-      - $CI_PROJECT_DIR/__build_info.json
-    reports:
-      dotenv: build.env
-
-.setup:
-  stage: setup
-  # this is now a noop and deprecated
+.test-base:
+  stage: test
+  needs: []
+  extends:
+    - .rules-always-but-not-on-tags
 
 .test:
-  stage: test
-  # do it also before semantic release, but not as a result of semantic release
-  <<: *only-default
-  <<: *not-after-semantic-release
-  cache:
-    <<: *cache-node-modules
+  extends: .test-base
   script:
-    - cd $APP_PATH
-    - if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi
-    - yarn install --frozen-lockfile
-    - yarn test
-  needs: []
-.lint:
+    - echo "not implemented"
+
+.lint-base:
   stage: test
-  # do it also before semantic release, but not as a result of semantic release
-  <<: *only-default
-  <<: *not-after-semantic-release
-  cache:
-    <<: *cache-node-modules
+  extends:
+    - .rules-always-but-not-on-tags
+
+.lint:
+  extends: .lint-base
   script:
-    - cd $APP_PATH
-    - if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi
-    - yarn install --frozen-lockfile
-    - yarn lint
-  needs: []
+    - echo "not implemented"
 
-.storybook-build:
+.app-build-base:
   stage: build
-  <<: *only-storybook
-  cache:
-    <<: *cache-node-modules
+  retry: 2
+  extends:
+    - .rules-always
   needs: ["setup-build-info"]
-  # don't do it after the semantic release commit, but do it after tag
-  script:
-    - cd $APP_PATH
-    - if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi
-    - yarn install --frozen-lockfile
-    - yarn build-storybook -c .storybook -s public -o .storybook-out --quiet
-  artifacts:
-    paths:
-      - $CI_PROJECT_DIR/.storybook-out
 
 .app-build:
-  stage: build
-  retry: 2
-  <<: *only-default
-  cache:
-    <<: *cache-node-modules
-  # don't do it after the semantic release commit, but do it after tag
+  extends: .app-build-base
   script:
-    - echo $BUILD_ID
-    - cd $APP_PATH
-    - if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi
-    - yarn install --frozen-lockfile
-    - yarn build
-  needs: ["setup-build-info"]
-  artifacts:
-    paths:
-      - $CI_PROJECT_DIR/__build_info.json
-      - $CI_PROJECT_DIR/dist
-      - $CI_PROJECT_DIR/.next
-
-.docker-build:
-  stage: docker-build
-  <<: *only-default
-  extends: .docker-build-app-base
-  needs: ["app-build"]
-
-.docker-build-storybook:
-  stage: docker-build
-  <<: *only-storybook
-  extends: .docker-build-storybook-base
-  needs: ["storybook-build"]
+    - echo "not implemented"
 
 # ----------------------
 # REVIEW APP
@@ -510,107 +104,82 @@ image: panterch/docker-ci-kubernetes-deploy
 
 .review-deploy:
   stage: deploy
-  <<: *only-on-merge-request
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
   retry: 2
   extends: .deploy-to-kubernetes-review
 
 .review-stop:
-  tags:
-  stage: review-stop
-  when: manual
-  extends: .kubernetes-review-stop
-  <<: *only-on-merge-request
-
-# on hotfix branches, do automatic review-stop
-.review-stop-automatic-on-hotfix:
   tags:
   stage: review-stop
   extends: .kubernetes-review-stop
-  only:
-    refs:
-      - /^[0-9]+\.([0-9]+|x)\.x$/ # hotfix branches
-
-.create-release-automatic:
-  stage: create-release
-  extends: .semantic-release-base
-  only:
-    refs:
-      - master
-      - main
-    variables:
-      - $AUTO_RELEASE == "true"
-  <<: *not-after-semantic-release
-
-.create-release:
-  stage: create-release
-  extends: .semantic-release-base
-  only:
-    refs:
-      - master
-      - main
-      - /^[0-9]+\.([0-9]+|x)\.x$/ # hotfix branches
-  <<: *not-after-semantic-release
-  when: manual
+  rules:
+    - if: $CI_COMMIT_BRANCH =~ /^[0-9]+\.([0-9]+|x)\.x$/ # automatic on hotfix branches
+      when: on_success
+      allow_failure: true
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+      when: manual
+      allow_failure: true
 
 .dev-deploy:
   stage: deploy
-  <<: *only-on-master
-  <<: *not-after-semantic-release
-  retry: 2
-  extends: .deploy-to-kubernetes-dev
-
-.dev-deploy-after-release:
-  stage: deploy
-  only: ["tags"]
-  # do not do the automatic release on hotfix branches
-  except:
-    variables:
-      - $CI_COMMIT_MESSAGE =~ /^chore\(maintenance\).*/ # do not do automatic release after a hotfix / maintenance release
-  extends: .deploy-to-kubernetes-dev
-
-.stage-deploy-automatic:
-  stage: deploy
-  only:
-    refs:
-      - tags
-    variables:
-      - $STAGING_ENABLED == "true"
-  # do not do the automatic release on hotfix branches
-  except:
-    variables:
-      - $CI_COMMIT_MESSAGE =~ /^chore\(maintenance\).*/ # do not do automatic release after a hotfix / maintenance release
-
-  extends: .deploy-to-kubernetes-stage
+  extends:
+    - .rules-dev-deploy
+    - .deploy-to-kubernetes-dev
 
 .stage-deploy:
   stage: deploy
-  only: ["tags"]
-  when: manual
-  extends: .deploy-to-kubernetes-stage
-
-.prod-deploy-automatic:
-  stage: deploy
-  only: ["tags"]
-  except:
-    variables:
-      - $STAGING_ENABLED == "true"
-      - $CI_COMMIT_MESSAGE =~ /^chore\(maintenance\).*/ # do not do automatic release after a hotfix / maintenance release
-
-  extends: .deploy-to-kubernetes-prod
+  extends:
+    - .rules-stage-deploy
+    - .deploy-to-kubernetes-stage
 
 .prod-deploy:
   stage: deploy
-  only: ["tags"]
-  when: manual
-  extends: .deploy-to-kubernetes-prod
+  extends:
+    - .rules-prod-deploy
+    - .deploy-to-kubernetes-prod
 
 .verify-base:
-  extends: .defaultEnv
   stage: verify
-  <<: *only-default
-  cache:
-    <<: *cache-node-modules
-    policy: pull
+
+.verify-review:
+  stage: verify
+  extends:
+    - .verify-base
+    - .env-review
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+  needs: ["review-deploy"]
+
+.verify-dev:
+  stage: verify
+  extends:
+    - .verify-base
+    - .env-dev
+  needs: ["dev-deploy"]
+  rules:
+    - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\).*/ # not after semantic release commit
+      when: never
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+
+.verify-stage:
+  stage: verify
+  extends:
+    - .verify-base
+    - .env-stage
+  needs: ["stage-deploy"]
+  rules:
+    - if: $STAGING_ENABLED == "true" && $CI_COMMIT_TAG
+
+.verify-prod:
+  stage: verify
+  extends:
+    - .verify-base
+    - .env-prod
+  needs: ["prod-deploy"]
+  rules:
+    - if: $CI_COMMIT_TAG
+
 #----------------------
 # DEFAULT STAGE DEFINITIONS
 #----------------------
@@ -660,29 +229,14 @@ review-deploy:
 review-stop:
   extends: .review-stop
 
-review-stop-automatic-on-hotfix:
-  extends: .review-stop-automatic-on-hotfix
-
 dev-deploy:
   extends: .dev-deploy
 
-dev-deploy-after-release:
-  extends: .dev-deploy-after-release
-
 create-release:
   extends: .create-release
 
-create-release-automatic:
-  extends: .create-release-automatic
-
 stage-deploy:
   extends: .stage-deploy
 
-stage-deploy-automatic:
-  extends: .stage-deploy-automatic
-
 prod-deploy:
   extends: .prod-deploy
-
-prod-deploy-automatic:
-  extends: .prod-deploy-automatic