###########################################################
#####                                                 #####
#####       DO  NOT  EDIT  THIS  FILE  BY  HAND       #####
#####                                                 #####
###########################################################
#####                                                 #####
##### This file is autogenerated from gitlab/.        #####
##### Edit those files and run `make gitlab` instead. #####
#####                                                 #####
###########################################################

spec:
  inputs:
    netlab:
      description: "Manually run netlab tests"
      type: boolean
      default: false
    build:
      description: "Manually run build tests"
      type: boolean
      default: false
    packages:
      description: "Manually run packaging (will run also build tests anyway)"
      type: boolean
      default: false
    docker-all:
      description: "Rebuild all dockers"
      type: boolean
      default: false
    docker-regex:
      description: "Rebuild dockers matching this regex"
      type: string
      default: 'NOTHING' # this is another hack because of gitlab syntax quirks
    docker-one:
      description: "Rebuild this one docker (keep empty for none)"
      type: string
      default: ''
      options:
      - ''
{%- for docker in [ { "name": "docbuilder" } ] + distros %}
      - {{ docker["name"] }}
{%- endfor %}

---

variables:
  DEBIAN_FRONTEND: noninteractive
  LC_ALL: C.UTF-8
  GIT_STRATEGY: fetch
  DOCKER_CMD: docker --config="$HOME/.docker/$CI_JOB_ID/"
  IMG_BASE: registry.nic.cz/labs/bird
  TOOLS_DIR: /home/gitlab-runner/bird-tools
  STAYRTR_BINARY: /usr/local/bin/stayrtr
  GITLAB_API_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/

stages:
  - consistency
  - image
  - build
  - pkg
  - install
  - test

## Common rule snippets
{%- set is_push =    '($CI_PIPELINE_SOURCE == "push")' %}
{%- set is_mreq =    '($CI_PIPELINE_SOURCE == "merge_request_event")' %}
{%- set is_manual =  '($CI_PIPELINE_SOURCE == "web")' %}
{%- set is_api =     '($CI_PIPELINE_SOURCE == "api")' %}
{%- set is_sched =   '($CI_PIPELINE_SOURCE == "schedule")' %}
{%- set is_stable =  '($CI_COMMIT_BRANCH =~ /' ~ stable_branch_regex ~ '/)' %}
{%- set not_stable =  '($CI_COMMIT_BRANCH !~ /' ~ stable_branch_regex ~ '/)' %}

{%- set is_release_commit =  '($CI_COMMIT_MESSAGE =~ /^NEWS and version update/)' %}
{%- set is_wip =     '($CI_COMMIT_MESSAGE =~ /^(fixup! )*WIP/)' %}
{%- set is_tag =     '$CI_COMMIT_TAG' %}

{%- set inputs_packages =    '"$[[ inputs.packages ]]" == "true"' %}
{%- set inputs_build =       '"$[[ inputs.build ]]" == "true"' %}
{%- set inputs_netlab =      '"$[[ inputs.netlab ]]" == "true"' %}
{%- set inputs_docker_all =  '"$[[ inputs.docker-all ]]" == "true"' %}

workflow:
  rules:
    - if: '{{ is_manual }}'
      variables:
        wf_kind: manual
        wf_prefix: '[MAN] '
        wf_desc: '$CI_COMMIT_TITLE'
    - if: '{{ is_sched }}'
      variables:
        wf_kind: scheduled
        wf_prefix: '[SCD] '
        wf_desc: '$CI_COMMIT_TITLE'
    - if: '{{ is_tag }}'
      variables:
        wf_kind: tag
        wf_prefix: '[TAG] '
        wf_desc: '$CI_COMMIT_TAG'
    - if: '{{ is_push }}'
      variables:
        wf_kind: regular
        wf_prefix: ''
        wf_desc: '$CI_COMMIT_TITLE'
    - if: '{{ is_stable }} && {{ is_release_commit }}'
      variables:
        wf_kind: release
        wf_prefix: '[RELEASE] '
        wf_desc: '$CI_COMMIT_BRANCH'
    - if: '{{ is_release_commit }}'
      variables:
        wf_kind: release-candidate
        wf_prefix: '[RC] '
        wf_desc: '$CI_COMMIT_BRANCH'
    - when: always
      variables:
        wf_kind: unknown
        wf_prefix: '[$CI_PIPELINE_SOURCE] '
        wf_desc: '$CI_COMMIT_TITLE'

  name: '$wf_prefix$wf_desc'

# Note: We would love to just write "if: $[[ inputs.packages ]]"
# but that's impossible because the value is expected to be a string.
#
# Should be fixable by several lines in
# lib/gitlab/ci/pipeline/expression/statement.rb
# but I don't have time to do that contribution to gitlab now.

{%- macro rule(when, yesno) -%}
  - if: '{{ when }}'
    when: {% if yesno %}on_success{% else %}never{% endif %}
{%- endmacro -%}

{%- macro rdef(yesno) -%}
  - when: {% if yesno %}on_success{% else %}never{% endif %}
{%- endmacro -%}

## Default rules for regular jobs
.default-rules:
# Manual, API and Scheduled jobs have an explicit input setting.
# Do not run anything unless explicitly requested for these.
  {{ rule(is_manual, False) }}
  {{ rule(is_api, False) }}
  {{ rule(is_sched, False) }}
# Do not run anything for WIP commits
  {{ rule(is_wip, False) }}
# Tags only happen on releases, don't run full automation
  {{ rule(is_tag, False) }}
  {{ rdef(True) }}

## Pipeline information dump
pipeline-infodump:
  stage: consistency
  image: registry.nic.cz/labs/bird:docbuilder
  script:
  - |
    echo "### Variables ###"
{%- for VAR in [
  "CI_JOB_ID",
  "CI_COMMIT_SHA",
  "CI_COMMIT_BRANCH",
  "CI_COMMIT_TAG",
  "CI_COMMIT_TITLE",
  "CI_COMMIT_MESSAGE",
  "CI_PIPELINE_SOURCE",
  "CI_MERGE_REQUEST_IID",
  "GITLAB_API_URL",
  "wf_kind",
] %}
    echo "\${{ VAR }} = ${{ VAR }}"
{%- endfor %}
    echo
    echo "### Inputs ###"
    echo "Netlab: $[[ inputs.netlab ]]"
    echo "Build: $[[ inputs.build ]]"
    echo "Packages: $[[ inputs.packages ]]"
    echo "Docker all: $[[ inputs.docker-all ]]"
    echo "Docker regex: $[[ inputs.docker-regex ]]"
    echo "Docker one: $[[ inputs.docker-one ]]"

## Pipeline consistency check
pipeline-uptodate:
  stage: consistency
  image: registry.nic.cz/labs/bird:docbuilder
  script:
  - autoreconf -i
  - ./configure
  - touch gitlab/template.yml.j2
  - make gitlab-venv
  - git status --porcelain
  - git diff --exit-code
  rules:
  - !reference [ .default-rules ]

## Build Docker consistency check
docker-uptodate:
  stage: consistency
  image: registry.nic.cz/labs/bird:docbuilder
  script:
  - autoreconf -i
  - ./configure
  - touch gitlab/data.yml.j2
  - make gitlab-docker VENV=1
  - touch gitlab/docker/docbuilder/Dockerfile # Docbuilder is a static image, not rebuilt by the templates
  - find gitlab/docker/ -type f -not -newer gitlab/data.yml.j2
  - '[ "`find gitlab/docker/ -type f -not -newer gitlab/data.yml.j2 | wc -l`" == "0" ]'
  - git status --porcelain
  - git diff --exit-code
  rules:
  - !reference [ .default-rules ]

## Consistency checks for stable branches
commit-messages:
  stage: consistency
  image: registry.nic.cz/labs/bird:docbuilder
  script:
  - tools/git-check-commits
  rules:
  {{ rule(is_stable, True) }}
  {{ rule(is_release_commit, True) }}
  {{ rdef(False) }}

## Tag check
tag-collect:
  stage: consistency
  image: registry.nic.cz/labs/bird:docbuilder
  script:
  - python3 -m venv venv
  - . venv/bin/activate
  - pip3 install requests
  - tools/git-check-tag-local $CI_COMMIT_TAG
  - tools/git-check-tag-ci $CI_COMMIT_SHA
  artifacts:
    paths:
      - obj/doc/bird-singlepage.html
      - obj/doc/prog-singlepage.html
      - bird-*.tar.gz
      - pkg/pkgs/*
      - pkg/srcpkgs/*
  rules:
  {{ rule(is_tag, True) }}
  {{ rdef(False) }}

############################
## Docker builder rebuild ##
############################
# We are running all the build / packaging tests in Dockers (unless otherwise)
# and these are rules to build these docker images. These are expensive to run
# and should run only if needed.
#
# Modify the appropriate dockerfile to rebuild these images

.docker: &docker-build
  stage: image
  needs: [] # docker-uptodate maybe?
  script:
  - $DOCKER_CMD login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.nic.cz
  # Make sure we refresh the base image if it updates (eg. security updates, etc)
  # If we do just the build, cache is always reused and the freshness of the
  # base image is never checked. However, pull always asks and updates the
  # image only if it changed ‒ therefore, the cache is used unless there's a
  # change.
  - $DOCKER_CMD pull `sed -ne 's/^FROM //p' "gitlab/docker/$IMG_NAME/Dockerfile"`
  - $DOCKER_CMD build -t "bird:$IMG_NAME" "gitlab/docker/$IMG_NAME"
  - $DOCKER_CMD tag "bird:$IMG_NAME" "$IMG_BASE:$IMG_NAME"
  - $DOCKER_CMD push "$IMG_BASE:$IMG_NAME"
  after_script:
  - rm -rf "$HOME/.docker/$CI_JOB_ID/" # cleanup the credentials
  tags:
  # That's Docker in Docker
  - dind

{% for docker in [ { "name": "docbuilder" } ] + distros %}
docker-{{ docker["name"] }}:
  variables:
    IMG_NAME: "{{ docker["name"] }}"

  rules:
    # Only rebuild if explicitly requested
  - if: '{{ inputs_docker_all }}'
  - if: '"{{ docker["name"] }}" =~ /$[[ inputs.docker-regex ]]/'
  - if: '"{{ docker["name"] }}" == "$[[ inputs.docker-one ]]"'
  {{ rdef(False) }}

  <<: *docker-build
{% endfor %}

#####################################################
## Linux distro build tests inside Docker builders ##
#####################################################

## Build stage
#
# Now we compile and run unit tests ... in every single distribution.
# Every task needs its docker, see above.

.build-base-rules:
  - if: '{{ inputs_build }}'
  - if: '{{ inputs_packages }}'
  - !reference [ .default-rules ]


.build: &build-base
  stage: build
  rules:
    - !reference [ .build-base-rules ]
  script:
    - if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then export BRANCH=mergerequest-$CI_MERGE_REQUEST_IID; else export BRANCH=$CI_COMMIT_BRANCH; fi
    - tools/version && ( echo -n "BIRD version "; tools/version ) > version-expected
    - |
      STABLE_VERSION="`cat VERSION`"
      if [ "$CI_COMMIT_MESSAGE" == "NEWS and version update
      " ]; then
        if [ "${STABLE_VERSION}" == "`tools/version`" ]; then
          echo "Stable version precheck OK"
        else
          echo "Stable version discrepancy: $STABLE_VERSION vs. $(tools/version)"
          exit 1
        fi
      else
        if grep -qF "BIRD version ${STABLE_VERSION}+branch" version-expected; then
          true
        else
          echo -n "Commit $CI_COMMIT_SHA message $CI_COMMIT_MESSAGE expects version "
          cat version-expected
          exit 1
        fi
      fi
    - |
      if [ -n "$CROSSBUILD_ARCH" ]; then
        dpkg-architecture -A $CROSSBUILD_ARCH > crossbuild.sh
        . crossbuild.sh
        export CONFIGURE_OPTIONS="--host=${DEB_TARGET_GNU_TYPE} ${CONFIGURE_OPTIONS}"
        export CROSS_RUN=qemu-${CROSSBUILD_ARCH}
      fi
    - autoreconf
    - ./configure CPPFLAGS="$CPPFLAGS" LDFLAGS="$LDFLAGS" ${CONFIGURE_OPTIONS}
    # Detect which make is available
    - MAKE=make
    - which gmake 2>/dev/null >/dev/null && MAKE=gmake
    - $MAKE VERBOSE=1
    - $MAKE VERBOSE=1 TEST_RETRY_VERBOSE=1 check
    # Build docs when tools are available
    - if which linuxdoc pdflatex sgmlsasp >/dev/null ; then $MAKE docs ; fi
    # Check that the reported version is the right one
    - ${CROSS_RUN} ./bird --version |& grep -v 'Cannot disable transparent huge pages' | tee obj/version-built
    - diff obj/version-built version-expected

.build-docker-linux-amd64: &build-docker-linux-amd64
  <<: *build-base
  tags:
    - docker
    - linux
    - amd64

{% for dist in distros %}
build-{{dist["name"]}}:
  <<: *build-docker-linux-amd64
  needs:
    - job: docker-{{ dist["name"] }}
      optional: true
  image: registry.nic.cz/labs/bird:{{ dist["name"] }}
{%- if 'variables' in dist %}
  variables:
{{ dist["variables"] | to_yaml | indent(4, true) }}
{%- endif %}
{%- if "crossarch" in dist %}
{% for ca in dist.crossarch %}
crossbuild-{{ ca }}-{{ dist["name"] }}:
  <<: *build-docker-linux-amd64
  needs:
    - job: docker-{{ dist["name"] }}
      optional: true
  image: registry.nic.cz/labs/bird:{{ dist["name"] }}
  variables:
    CROSSBUILD_ARCH: {{ ca }}
{%- if 'variables' in dist %}
{{ dist["variables"] | to_yaml | indent(4, true) }}
{%- endif %}
{% endfor %}
{%- endif %}
{% endfor %}

#######################################################
## A special task for preparing the release archives ##
#######################################################
build-release:
  image: registry.nic.cz/labs/bird:docbuilder
  needs: 
    - job: docker-docbuilder
      optional: true
  stage: build
  tags:
    - docker
    - linux
  script:
    - if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then export BRANCH=mergerequest-$CI_MERGE_REQUEST_IID; else export BRANCH=$CI_COMMIT_BRANCH; fi
    - autoreconf
    - ./configure --with-protocols= --disable-client
    - make obj/doc/bird-singlepage.html obj/doc/prog-singlepage.html
    - if grep '<pre><code>PARSER FAILED ' obj/doc/bird-singlepage.html obj/doc/prog-singlepage.html; then echo "Documentation build is broken!"; false; fi
    - tools/make-archive
  artifacts:
    paths:
      - obj/doc/bird-singlepage.html
      - obj/doc/prog-singlepage.html
      - bird-*.tar.gz
    expire_in: 1 day
  rules: !reference [ .build-base-rules ]

# Packaging rules
# As we support some ancient versions of different distributions,
# we need to keep several different machineries. It's not so bad
# but it's bad nevertheless.
#
# We do NOT build separate documentation packages in these rules.

.pkg-base: &pkg-base
  rules:
  - if: '{{ inputs_packages }}'
  {{ rule(is_tag, False) }}
  {{ rule(is_release_commit, True) }}
  {{ rule(not_stable, False) }}
  - !reference [ .default-rules ]

.pkg-deb: &pkg-deb
  <<: *pkg-base
  stage: pkg
  script:
    - if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then export BRANCH=mergerequest-$CI_MERGE_REQUEST_IID; else export BRANCH=$CI_COMMIT_BRANCH; fi
    - tools/make-deb
  artifacts:
    paths:
      - pkg/pkgs/*
      - pkg/srcpkgs/*

.pkg-rpm: &pkg-rpm
  <<: *pkg-base
  stage: pkg
  script:
    - if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then export BRANCH=mergerequest-$CI_MERGE_REQUEST_IID; else export BRANCH=$CI_COMMIT_BRANCH; fi
    - STABLE_BUILDDIR=true tools/make-rpm
  artifacts:
    paths:
      - pkg/pkgs/*
      - pkg/srcpkgs/*


{% for dist in distros %}
pkg-{{ dist["name"] }}:
  <<: *pkg-{{ dist["type"] }}
  needs:
    - job: build-{{ dist["name"] }}
      artifacts: false
    - job: build-release
  image: registry.nic.cz/labs/bird:{{ dist["name"] }}
{%- if 'variables' in dist %}
  variables:
{{ dist["variables"] | to_yaml | indent(4, true) }}
{%- endif %}
{%- if "crossarch" in dist %}
{% for ca in dist.crossarch %}
crosspkg-{{ ca }}-{{ dist["name"] }}:
  <<: *pkg-{{ dist["type"] }}
  needs:
    - job: crossbuild-{{ ca }}-{{ dist["name"] }}
      artifacts: false
    - job: build-release
  image: registry.nic.cz/labs/bird:{{ dist["name"] }}
  variables:
    CROSSBUILD_ARCH: {{ ca }}
{%- if 'variables' in dist %}
{{ dist["variables"] | to_yaml | indent(4, true) }}
{%- endif %}
{% endfor %}
{%- endif %}
{% endfor %}

## Package installability checks

.install-deb: &install-deb
  <<: *pkg-base
  stage: install
  script:
    # setup crossbuild architecture
    - |
      if [ -n "$CROSSBUILD_ARCH" ]; then
        dpkg-architecture -A $CROSSBUILD_ARCH > crossbuild.sh
        . crossbuild.sh
        export CROSS_RUN=qemu-${CROSSBUILD_ARCH}
      fi
    # check that bird is _not_ installed now and no user or group bird exists
    - |
      if bird --version >/dev/null 2>&1; then
        echo "Error: BIRD unexpectedly installed"
        exit 1
      fi
      if id -g bird >/dev/null 2>&1; then
        echo "Error: User group 'bird' unexpectedly exist before installation"
        exit 1
      fi
      if id -u bird >/dev/null 2>&1; then
        echo "Error: User 'bird' unexpectedly exist before installation"
        exit 1
      fi
    # install packages
    - find pkg/pkgs/ -type f -name '*.deb' '(' -ls -exec dpkg -i '{}' ';' ')'
    # test that installation is successful
    - ./tools/test-install "$CI_COMMIT_MESSAGE"

.install-rpm: &install-rpm
  <<: *pkg-base
  stage: install
  script:
    # check that bird is _not_ installed now and no user or group bird exists
    - |
      if bird --version >/dev/null 2>&1; then
        echo "Error: BIRD unexpectedly installed"
        exit 1
      fi
      if id -g bird >/dev/null 2>&1; then
        echo "Error: User group 'bird' exist before installation"
        exit 1
      fi
      if id -u bird >/dev/null 2>&1; then
        echo "Error: User 'bird' exist before installation"
        exit 1
      fi
    # install packages
    - find pkg/pkgs/ -type f -name '*.rpm' '(' -ls -exec $RPM_INSTALL_CMD '{}' ';' ')'
    # test that installation is successful
    - ./tools/test-install "$CI_COMMIT_MESSAGE"

{% for dist in distros %}
install-{{ dist["name"] }}:
  <<: *install-{{ dist["type"] }}
  needs:
    - job: pkg-{{ dist["name"] }}
  image: registry.nic.cz/labs/bird:{{ dist["name"] }}
  variables:
{%- if dist["name"][:12] == "opensuse-16." %}
    RPM_INSTALL_CMD: zypper --non-interactive --no-gpg-checks in
{%- elif dist["type"] == "rpm" %}
    RPM_INSTALL_CMD: rpm -i
{%- endif %}

{%- for ca in dist.crossarch %}
crossinstall-{{ ca }}-{{ dist["name"] }}:
  <<: *install-{{ dist["type"] }}
  needs:
    - job: crosspkg-{{ ca }}-{{ dist["name"] }}
  image: registry.nic.cz/labs/bird:{{ dist["name"] }}
  variables:
{%- if dist["name"][:12] == "opensuse-16." %}
    RPM_INSTALL_CMD: zypper --non-interactive --no-gpg-checks in
{%- elif dist["type"] == "rpm" %}
    RPM_INSTALL_CMD: rpm -i
{%- endif %}
    CROSSBUILD_ARCH: {{ ca }}
{%- endfor %}
{% endfor %}

###################################
## Non-linux build tests in QEMU ##
###################################
.build-birdlab-base: &build-birdlab-base
  <<: *build-base
  stage: build
  needs: []
#  script:
#    - export BRANCH=$CI_COMMIT_BRANCH
#    - autoreconf
#    - ./configure $CONFIGURE_OPTIONS
#    - gmake
#    - gmake check

{% for test in birdlab %}
build-birdlab-{{ test['name'] }}:
  <<: *build-birdlab-base
{%- if 'variables' in test %}
  variables:
{{ test['variables'] | to_yaml | indent(4, true) }}{%- endif %}
  tags:
    - birdlab-{{ test['dist'] if 'dist' in test else test['name'] }}
    - amd64
{% endfor %}

#########################
## Partial build tests ##
#########################

.build-only: &build-only
  <<: *build-docker-linux-amd64
  image: registry.nic.cz/labs/bird:{{ partial_build_image["name"] }}
  needs:
    - job: docker-{{ partial_build_image["name"] }}
      optional: true

{% for test in partial_build %}
partial-build-linux-{{ test["protocols"] }}:
  <<: *build-only
  variables:
    CONFIGURE_OPTIONS: --with-protocols={{ test["protocols"] }}
{% endfor %}

################################
## Netlab functionality tests ##
################################

.netlab-rules: &netlab-rules
  rules:
    - if: '{{ inputs_netlab }}'
    - !reference [ .default-rules ]

build-netlab:
  <<: *netlab-rules
  stage: build
  needs: []
  variables:
    BDIR: build-netlab
  tags:
    - netlab
    - amd64
  script:
    - if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then export BRANCH=mergerequest-$CI_MERGE_REQUEST_IID; else export BRANCH=$CI_COMMIT_BRANCH; fi
    - autoreconf
    - mkdir $BDIR
    - cd $BDIR
    - ../configure
    - make
  artifacts:
    paths:
      - $BDIR/bird
      - $BDIR/birdc
    expire_in: 2 hours

.netlab-test: &test-base
  <<: *netlab-rules
  stage: test
  needs: [build-netlab]
  tags:
    - netlab
    - amd64
  script:
    - sudo rm -rf netlab-failure
    - DIR=$(pwd)
    - cd $TOOLS_DIR
    - sudo git clean -fx
    - sudo git checkout -f master
    - git pull --ff-only
    - "mv $DIR/build-netlab/* netlab/common/"
    - ln -s $STAYRTR_BINARY netlab/common/stayrtr
    - cd netlab
    - sudo ./stop
    - ulimit -f 1048576 # Protect the filesystem from overflowing by log bloat
    - sudo ./runtest -s v2 -m check $TEST_NAME
  after_script:
    - DIR=$(pwd)
    - cd $TOOLS_DIR/netlab
    - sudo ./stop || echo "Stop failed"
    - "mkdir -p $DIR/netlab-failure"
    - git status --porcelain > $DIR/netlab-failure.log
    - for f in $(git status --porcelain | sed -rn 's#^.[^DRT] netlab/##p'); do mkdir -p $DIR/netlab-failure/$(dirname $f); sudo chmod a+rw $f; sudo mv $f $DIR/netlab-failure/$(dirname $f); done
  artifacts:
    when: on_failure
    untracked: true

{% for test in netlab %}
test-{{ test["name"] }}:
  <<: *test-base
  variables:
    TEST_NAME: cf-{{ test["name"] }}
{% endfor %}
