Création de pipelines réutilisables pour GitLab CI dans Bash

Au cours des dernières années, je suis devenu très friand de GitLab CI . Principalement pour sa simplicité et sa fonctionnalité. Il suffit de créer un fichier à la racine du référentiel .gitlab-ci.yml



, d'y ajouter quelques lignes de code, et le prochain commit démarrera le pipeline avec un ensemble de travaux qui exécuteront les commandes spécifiées.





Et si vous ajoutez les capacités d' inclusion et d' extension à cela , vous pouvez faire des choses assez intéressantes: créer des modèles de travaux et des pipelines, les déplacer dans des référentiels séparés et les réutiliser dans différents projets sans copier le code.





Mais malheureusement, tout n'est pas aussi rose que nous le souhaiterions. L'instruction script



dans GitLab CI est de très bas niveau. Il exécute simplement les commandes qui lui sont données sous forme de chaînes. Il n'est pas très pratique d'écrire de gros scripts dans YAML. Au fur et à mesure que la logique devient plus complexe, le nombre de scripts augmente, ils se mélangent avec YAML rendant les configurations illisibles et compliquant leur maintenance.





J'ai vraiment manqué un mécanisme qui simplifierait le développement de gros scripts. En conséquence, un microframework pour le développement de GitLab CI est né, dont je veux parler dans cet article (en utilisant l'exemple d'un simple pipeline pour construire des images docker).





Exemple: création d'images docker dans GitLab

Je souhaite examiner le processus de création d'un pipeline à l'aide d'un exemple de tâche simple que j'ai souvent rencontrée avec différentes équipes: créer des images docker de base dans GitLab pour les réutiliser.





Par exemple, une équipe écrit des microservices et souhaite utiliser sa propre image de base pour tous avec un ensemble d'utilitaires de débogage préinstallés.





Ou un autre exemple, une équipe écrit des tests et souhaite utiliser les services directement dans GitLab pour créer une base de données temporaire (ou une file d'attente, ou autre chose) pour eux en utilisant leur propre image.





, docker- GitLab . .gitlab-ci.yml



:





services:
  - docker:dind

Build:
  image: docker
  script:
    - |
      docker login "$CI_REGISTRY" \
        --username "$CI_REGISTRY_USER" \
        --password "$CI_REGISTRY_PASSWORD"

      docker build \
        --file "Dockerfile" \
        --tag "$CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG" .

      docker push "$CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG"
      
      



Build



, GitLab Container Registry, . , ( , ).





, , :





  • , dockerfiles



    .





  • :





    • Build All



      - dockerfiles



      . ( ).





    • Build Changed



      - dockerfiles



      , . ( ) .





. . , .





:









, .NET.





:









docker-, Container Registry :





, .





1: " "

. .gitlab-ci.yml



. :





services:
  - docker:dind

stages:
  - Build

Build All:
  stage: Build
  image: docker
  when: manual
  script:
    - |
      dockerfiles=$(find "dockerfiles" -name "*.Dockerfile" -type f)

      docker login "$CI_REGISTRY" \
        --username "$CI_REGISTRY_USER" \
        --password "$CI_REGISTRY_PASSWORD"

      for dockerfile in $dockerfiles; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
      done

Build Changed:
  stage: Build
  image: docker
  only:
    changes:
      - 'dockerfiles/*.Dockerfile'
      - 'dockerfiles/**/*.Dockerfile'
  script:
    - |
      apk update
      apk add git #  ,     ,    ...

      dockerfiles=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')

      docker login "$CI_REGISTRY" \
        --username "$CI_REGISTRY_USER" \
        --password "$CI_REGISTRY_PASSWORD"

      for dockerfile in $dockerfiles; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
      done
      
      



:





  • ( step1)









:





  • Build All



    , :





    • , .. when: manual



      .





    • :





      • find "dockerfiles" -name "*.Dockerfile" -type f







  • Build Changed



    , :





    • , .. only:changes



      .





    • , :





      • git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile'







  • , , , dockerfiles/



    .Dockerfile



    , GitLab Container Registry.





:





  • ( )





  • , .





GitLab CI .





GitLab CI Bootstrap

GitLab CI Bootstrap - GitLab CI. :





  • ( .gitlab-ci.yml



    ) (bash shell), YAML .





  • , .





  • ( ) , .





GitLab CI Bootstrap bootstrap.gitlab-ci.yml, .gitlab-ci.yml



include. . include:local:





include:
  - local: 'bootstrap.gitlab-ci.yml'
      
      



.bootstrap



, :





.bootstrap:
  before_script:
    - |
     	...
      
      



.bootstrap



, extends:





example:
  extends: '.bootstrap'
  script:
    - '...'
      
      



( ), GitLab.





. . , , .





bash shell. git, . .bootstrap



docker- .





, , docker-.





2:

, .bootstrap



.gitlab-ci.sh



. , .





, , .gitlab-ci.yml



:





.gitlab-ci.yml



:





include:
  - project: '$CI_PROJECT_NAMESPACE/bootstrap'
    ref: 'master'
    file: 'bootstrap.gitlab-ci.yml'

services:
  - docker:dind

stages:
  - Build

Build All:
  stage: Build
  image: docker
  extends: .bootstrap
  when: manual
  script:
    - search_all_dockerfiles_task
    - build_and_push_dockerfiles_task

Build Changed:
  stage: Build
  image: docker
  extends: .bootstrap
  only:
    changes:
      - 'dockerfiles/*.Dockerfile'
      - 'dockerfiles/**/*.Dockerfile'
  script:
    - install_git_task
    - search_changed_dockerfiles_task
    - build_and_push_dockerfiles_task
      
      



.gitlab-ci.sh



:





DOCKERFILES=""

function search_all_dockerfiles_task() {
    DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}

function search_changed_dockerfiles_task() {
    DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}

function install_git_task() {
    #  ,     ,    ...
    apk update
    apk add git
}

function build_and_push_dockerfiles_task() {
    docker login "$CI_REGISTRY" \
        --username "$CI_REGISTRY_USER" \
        --password "$CI_REGISTRY_PASSWORD"

    for dockerfile in $DOCKERFILES; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
    done
}
      
      



:





gitlab-ci.sh



, .gitlab-ci.yml



. search_all_dockerfiles_task



search_changed_dockerfiles_task



DOCKERFILES



, build_and_push_dockerfiles_task



.





3:

, , . , , . , - , , .





include.





. , , , - , . , .





, .bootstrap



CI_IMPORT



, : CI_{module}_PROJECT



, CI_{module}_REF



CI_{module}_FILE



, {module}



- CI_IMPORT



( ).





, .gitlab-ci.sh



, .





:





.gitlab-ci.yml



:





include:
  - project: '$CI_PROJECT_NAMESPACE/dockerfiles-example-ci'
    ref: 'step3'
    file: 'dockerfiles.gitlab-ci.yml'
      
      



:





dockerfiles.gitlab-ci.yml



( .gitlab-ci.yml



):





include:
  - project: '$CI_PROJECT_NAMESPACE/bootstrap'
    ref: 'master'
    file: 'bootstrap.gitlab-ci.yml'

services:
  - docker:dind

stages:
  - Build

variables:
  CI_DOCKERFILES_PROJECT: '$CI_PROJECT_NAMESPACE/dockerfiles-example-ci'
  CI_DOCKERFILES_REF: 'step3'
  CI_DOCKERFILES_FILE: 'dockerfiles.gitlab-ci.sh'

Build All:
  stage: Build
  image: docker
  extends: .bootstrap
  variables:
    CI_IMPORT: dockerfiles
  when: manual
  script:
    - search_all_dockerfiles_task
    - build_and_push_dockerfiles_task

Build Changed:
  stage: Build
  image: docker
  extends: .bootstrap
  variables:
    CI_IMPORT: dockerfiles
  only:
    changes:
      - 'dockerfiles/*.Dockerfile'
      - 'dockerfiles/**/*.Dockerfile'
  script:
    - search_changed_dockerfiles_task
    - build_and_push_dockerfiles_task
      
      



dockerfiles.gitlab-ci.sh



( .gitlab-ci.sh



):





DOCKERFILES=""

function search_all_dockerfiles_task() {
    DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}

function search_changed_dockerfiles_task() {
    DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}

function build_and_push_dockerfiles_task() {
    docker login "$CI_REGISTRY" \
        --username "$CI_REGISTRY_USER" \
        --password "$CI_REGISTRY_PASSWORD"

    for dockerfile in $DOCKERFILES; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
    done
}
      
      



:





Build All



Build Changed



CI_IMPORT



dockerfiles



. : CI_DOCKERFILES_PROJECT



, CI_DOCKERFILES_REF



CI_DOCKERFILES_FILE



, .





, dockerfiles.gitlab-ci.sh



, $CI_PROJECT_NAMESPACE/dockerfiles-example-ci



, step3



.





, install_git_task



. git , .. git git .





4:

. , .





dockerfiles.gitlab-ci.sh



. , .





, include



, , . , . include



, .





, , include



.





dockerfiles.gitlab-ci.sh



:





dockerfiles.gitlab-ci.sh



:





include "tasks/search.sh"
include "tasks/docker.sh"
      
      



tasks/search.sh



:





DOCKERFILES=""

function search_all_dockerfiles_task() {
    DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}

function search_changed_dockerfiles_task() {
    DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}
      
      



tasks/docker.sh



:





function build_and_push_dockerfiles_task() {
    docker login "$CI_REGISTRY" \
        --username "$CI_REGISTRY_USER" \
        --password "$CI_REGISTRY_PASSWORD"

    for dockerfile in $DOCKERFILES; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
    done
}
      
      



:





GitLab CI bash GitLab CI Bootstrap.





. :





  • . .. , . GitLab CI.





  • . , Python C#.





  • . , .





, .





, 1.0. , . , .





Sources: peuvent être trouvées ici: https://gitlab.com/chakrygin/bootstrap





Dépôts avec des exemples ici:








All Articles