gitlab CI/CD提交自动sonarqube 扫描sendgrid发送邮件给提交者

内容目录

1.安装配置gitlab Runner

1. 注册Runner

注册一个新的Runner。具体步骤如下:

  1. 在服务器上安装GitLab Runner,通常使用以下命令:
   # 更新apt包列表
   sudo apt update
   # 安装GitLab Runner
   sudo apt install gitlab-runner
  1. 注册Runner:
   sudo gitlab-runner register

在此过程中,系统将提示你输入GitLab实例的URL、注册令牌、Runner描述、标签、执行器(比如docker执行器)等信息,这些在gitlab>>设置>>概览>>runner标签页获取。

  1. 启动Runner:
    如果gitlab项目 clone地址跟gitlab 服务器内网地址不一样,可以extra_hosts添加docker hosts映射
    防止docker无法clone,拉取
vim  /etc/gitlab-runner/config.toml 
concurrent = 1
check_interval = 0
connection_max_age = "15m0s"
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = nux002"
  url = "http://192.168.162.19"
  id = 2
  token = "izB6xxxxxxfTu5_Hd"
  token_obtained_at = 2024-11-05T02:05:58Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    MaxUploadedArchiveSize = 0
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    extra_hosts = ["nux002.xxx.com:192.168.162.19"]
    tls_verify = false
    image = "sonarsource/sonar-scanner-cli:latest"
    privileged = false
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
    network_mtu = 0
   sudo gitlab-runner start

安装报错解决

apt update
Hit:1 http://mirrors.tuna.tsinghua.edu.cn/ubuntu focal InRelease                                                                   
Hit:2 http://mirrors.tuna.tsinghua.edu.cn/ubuntu focal-updates InRelease                                                           
Hit:3 http://mirrors.tuna.tsinghua.edu.cn/ubuntu focal-backports InRelease                           
Hit:4 http://mirrors.tuna.tsinghua.edu.cn/ubuntu focal-security InRelease                      
Hit:6 https://jfrog.bintray.com/artifactory-debs focal InRelease                                      
Hit:7 https://packages.gitlab.com/runner/gitlab-runner/ubuntu focal InRelease                                    
Get:5 https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal InRelease [23.3 kB]
Err:5 https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal InRelease
  The following signatures were invalid: EXPKEYSIG 3F01618A51312F3F GitLab B.V. (package repository signing key) <packages@gitlab.com>
Fetched 23.3 kB in 5s (5,152 B/s)
Reading package lists... Done
Building dependency tree       
Reading state information... Done
10 packages can be upgraded. Run 'apt list --upgradable' to see them.
W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal InRelease: The following signatures were invalid: EXPKEYSIG 3F01618A51312F3F GitLab B.V. (package repository signing key) <packages@gitlab.com>
W: Failed to fetch https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/dists/focal/InRelease  The following signatures were invalid: EXPKEYSIG 3F01618A51312F3F GitLab B.V. (package repository signing key) <packages@gitlab.com>
W: Some index files failed to download. They have been ignored, or old ones used instead.

你遇到的错误信息表明GitLab的APT仓库的签名密钥已经过期,因此无法验证包的完整性。解决这个问题需要更新GitLab的签名密钥。请按照以下步骤进行操作:

1. 更新GitLab的签名密钥

可以通过运行以下命令来更新GitLab的APT密钥:

curl -s https://packages.gitlab.com/gpg.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/gitlab.gpg

这个命令会从GitLab的官方网站获取最新的GPG密钥,并将其保存到系统的信任密钥文件中。

2. 清理旧密钥

如果你在更新密钥时想要清理旧的密钥,可以使用以下命令来查看并删除过期的密钥:

sudo apt-key list

查看到GitLab相关的密钥后,可以通过以下命令删除:

sudo apt-key del <密钥ID>

请将<密钥ID>替换为你想要删除的密钥的实际ID。

3. 再次更新APT源

更新密钥后,运行以下命令以更新APT源:

sudo apt update

4. 安装GitLab Runner

如果更新成功,则可以继续安装GitLab Runner:

sudo apt install gitlab-runner

5. 检查安装状态

安装完成后,确认GitLab Runner是否安装成功:

gitlab-runner --version

2.runner执行器选择

注册时提示

Enter an executor: docker+machine, kubernetes, instance, shell, ssh, virtualbox, docker, docker-windows, docker-autoscaler, custom, parallels:

在注册GitLab Runner时,系统会要求选择一个执行器(executor),用于指定Runner将如何执行作业。每种执行器代表一种运行方式。以下是常用执行器的介绍和适用情况:

常用执行器选项

  1. shell
    直接在注册Runner的主机上运行任务。适用于简单的任务,不需要容器化,且主机已配置好运行所需的环境。

    Enter an executor: shell
  2. docker
    使用Docker容器来运行每个作业。适合需要隔离环境的任务,每个任务会在独立的容器中执行。需要主机上已安装Docker。

    Enter an executor: docker
  3. docker+machine
    使用Docker Machine自动创建并管理虚拟机来运行容器,适合需要动态扩展的CI/CD环境。

    Enter an executor: docker+machine
  4. kubernetes
    在Kubernetes集群中运行任务,适合大规模、容器化的应用部署。需要Kubernetes集群的支持。

    Enter an executor: kubernetes
  5. ssh
    通过SSH连接到远程服务器执行任务。适用于已配置的远程服务器上运行作业。

    Enter an executor: ssh

如何选择

  • 如果在Docker环境中运行作业,推荐使用 docker
  • 如果是在本地服务器上直接运行,可以选择 shell
  • 如果有Kubernetes集群,选择 kubernetes

例如,我选选择 docker 执行器(容器执行服务器环境干扰小),输入:

docker

然后按 Enter

3.runner注册完成后配置

共享runner

如果需要全部项目使用这个runner
gitlab runner界面点击进入runner
去掉
此Runner仅在受保护分支上触发的流水线上运行
当Runner被锁定时,不能将其分配给其他项目

增加runner使用项目

Restrict projects for this runner
下搜索gitlab项目点击启用

2. gitlab-ci.yml编写

gitlab项目下增加.gitlab-ci.yml文件,这样每次提交触发自动扫描
sonarqube扫描不通过这边使用sendgrid发送邮件通知
所以模板为

stages:
  - build      # 添加 build 阶段
  - sonar
  - notification

variables:
  SONAR_HOST_URL: "http://192.168.162.20:9000"   # 替换为SonarQube服务器地址
  SONAR_PROJECT_KEY: "$CI_PROJECT_NAME"                      # 替换为SonarQube项目Key
  SONAR_PROJECT_NAME: "$CI_PROJECT_NAME"                     # 替换为SonarQube项目名称
  SONAR_TOKEN: "$SONAR_TOKEN"                    # 在GitLab CI/CD中设置的SonarQube Token
  GIT_COMMITTER_EMAIL: "$CI_COMMITTER_EMAIL"     # GitLab自动提供的提交者邮箱
  SENDGRID_API_KEY: "$SENDGRID_API_KEY"          # 在GitLab CI/CD中设置的SendGrid API Key

# SonarQube 扫描阶段
sonarqube-scan:
  stage: sonar
  image: sonarsource/sonar-scanner-cli:latest
  script:
    - mkdir -p build  # 创建空的 build 目录
    - |
      sonar-scanner \
        -Dsonar.projectKey=$SONAR_PROJECT_KEY \
        -Dsonar.host.url=$SONAR_HOST_URL \
        -Dsonar.login=$SONAR_TOKEN \
        -Dsonar.sources=. \    #扫描路径
        -Dsonar.inclusions=**/* \
        -Dsonar.java.binaries=build  # 指定为刚创建的空目录,如果不是java项目可以注释此行
        #-Dsonar.language=vue \
        #-Dsonar.sourceEncoding=UTF-8
        #-Dsonar.exclusions=**/*.java  # 排除所有Java文件
  only:
    - branches   # 仅在 dev 分支触发扫描

# 发送邮件阶段
send-email:
  stage: notification
  image: bitnami/minideb:latest
  script:
    - |
      set -x
      install_packages jq curl ca-certificates
      # 获取SonarQube质量门禁状态和详细信息
      export RECIPIENT_EMAIL=$(echo "$CI_COMMIT_AUTHOR" | sed -n 's/.*<\(.*\)>.*/\1/p')
      RECIPIENT_EMAILS="$RECIPIENT_EMAIL"
      CC_EMAILS="$GLOBAL_CC_EMAILS"
      # 获取 SonarQube 质量门禁状态和详细信息
      quality_gate_info=$(curl -s -u "$SONAR_TOKEN:" "$SONAR_HOST_URL/api/qualitygates/project_status?projectKey=$SONAR_PROJECT_KEY&branch=$CI_COMMIT_BRANCH")
      status=$(echo "$quality_gate_info" | jq -r '.projectStatus.status')
      period_date=$(echo "$quality_gate_info" | jq -r '.projectStatus.period.date')

      # 准备邮件内容
      email_content="项目 '$SONAR_PROJECT_NAME' 分支 '$CI_COMMIT_BRANCH' 的 SonarQube 扫描质量门禁状态: $status\n评估日期: $period_date\n\n详细信息:\n"
      # 从子进程中读取循环内容,并追加到 email_content
      conditions=$(echo "$quality_gate_info" | jq -c '.projectStatus.conditions[]')
      while read -r condition; do
        rule=$(echo "$condition" | jq -r '.metricKey')
        condition_status=$(echo "$condition" | jq -r '.status')
        comparator=$(echo "$condition" | jq -r '.comparator')
        error_threshold=$(echo "$condition" | jq -r '.errorThreshold')
        actual_value=$(echo "$condition" | jq -r '.actualValue')
        email_content+="指标: $rule, 比较符: $comparator, 错误阈值: $error_threshold, 实际值: $actual_value, 状态: $condition_status\n"
      done <<< "$conditions"
      # 准备邮件内容并追加 SonarQube 项目链接
      sonar_project_url="http://sonarqube_url:9000/dashboard?branch=$CI_COMMIT_BRANCH&id=$SONAR_PROJECT_KEY"
      email_content+="\n SonarQube 项目地址: $sonar_project_url\n\n"
      # 打印 email_content 以便检查内容是否正确追加
      echo -e "$email_content"

      # 判断质量门禁状态,未通过时发送邮件
      if [ "$status" != "OK" ]; then
        echo "SonarQube质量门禁未通过,发送邮件通知..."

        # 生成收件人和抄送人的 JSON 数组
        # 使用 jq 处理邮件地址并去除换行符
        recipients=$(echo "$RECIPIENT_EMAILS" | jq -R -s 'split(",") | map({"email": (. | gsub("\n";""))})')
        # 如果收件人在 CC 中,则移除
      if [[ -n "$CC_EMAILS" ]]; then
        CC_EMAILS=$(echo "$CC_EMAILS" | sed "s/$RECIPIENT_EMAIL//g" | sed 's/,,/,/g' | sed 's/^,//;s/,$//')
      fi
      # 检查 CC_EMAILS 是否为空,为空则不包括 cc 字段
      if [ -n "$CC_EMAILS" ]; then
        cc_list=$(echo "$CC_EMAILS" | jq -R -s 'split(",") | map({"email": (. | gsub("\n";""))})')
        cc_field='"cc": '"$cc_list"','
      else
        cc_field=''
      fi

        # 调用 SendGrid API 发送邮件
        curl -s --request POST --url https://api.sendgrid.com/v3/mail/send \
          --header "Authorization: Bearer $SENDGRID_API_KEY" \
          --header 'Content-Type: application/json' \
          --data '{
            "personalizations": [
              {
                "to": '"$recipients"',
                '"$cc_field"'
                "subject": "SonarQube 扫描未通过通知"
              }
            ],
            "from": {"email": "gitlab@noreply.xxxx.com"},
            "content": [
              {
                "type": "text/plain",
                "value": "'"$email_content"'"
              }
            ]
          }'
      fi
  dependencies:
    - sonarqube-scan
  only:
    - branches  # 仅在指定分支触发

模版中使用了变量,在girlab>>设置>> CICD >>变量 中增加
GLOBAL_CC_EMAILS : 全局抄送邮件列表,英文逗号分隔
SENDGRID_API_KEY : sendgrid密钥
SONAR_TOKEN : sonarqube api 密钥
其他通知方式,可以让ai帮忙修改

多项目共用配置合并扫描主分支不扫描修改版.gitlab.yml

适用于多项目共用.gitlab-ci.yml,不然实际使用中几十个项目挨个改真的吐血

1.变量 env

GLOBAL_CC_EMAILS : 全局抄送邮件列表,英文逗号分隔
SENDGRID_API_KEY : sendgrid密钥
SONAR_TOKEN : sonarqube api 密钥
除了以上新增gitlab全局变量
GITLAB_INTERNAL_URL gitlab内网地址,适用于内外网隔离地址不一样的企业网络状况
GITLAB_TOKEN gitlab API token 用于获取提交合并的请求人 审核人 指派人的邮箱
SONAR_PUBLIC_URL sonarqube公网地址,适用于内外网隔离地址不一样的企业网络状况

2.gitlab创建共享项目

Visibility Level: 选择 Internal。
ps: gitlabURL/fynn/devops

创建共用CI脚本

gitlabURL/fynn/devops 创建文件夹CI
CI文件夹下创建ci_path_all.yml 共享文件

stages:
  - build
  - sonar
  - notification

variables:
  SONAR_HOST_URL: "http://192.168.162.20:9000"
  SONAR_PROJECT_KEY: "$CI_PROJECT_NAME"
  SONAR_PROJECT_NAME: "$CI_PROJECT_NAME"
  SONAR_TOKEN: "$SONAR_TOKEN"
  GIT_COMMITTER_EMAIL: "$CI_COMMITTER_EMAIL"
  SENDGRID_API_KEY: "$SENDGRID_API_KEY"

# SonarQube 扫描阶段
sonarqube-scan:
  stage: sonar
  image: sonarsource/sonar-scanner-cli:latest
  script:
    - mkdir -p build
    - |
      sonar-scanner \
        -Dsonar.projectKey=$SONAR_PROJECT_KEY \
        -Dsonar.host.url=$SONAR_HOST_URL \
        -Dsonar.login=$SONAR_TOKEN \
        $SONAR_OPTIONS
        #-Dsonar.sources=.
        #-Dsonar.inclusions=**/*.java,**/*.yml \
        #-Dsonar.java.binaries=build  # 指定为刚创建的空目录
        #-Dsonar.language=vue \
        #-Dsonar.sourceEncoding=UTF-8
        #-Dsonar.exclusions=**/*.java  # 排除所有Java文件
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: never # 主分支跳过
    - if: $CI_PIPELINE_SOURCE == "push"
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 通知阶段
send-email:
  stage: notification
  image: bitnami/minideb:latest
  script:
    - |
      set -x
      install_packages jq curl ca-certificates

      # 判断是否是合并请求
      if [[ -n "$CI_MERGE_REQUEST_IID" ]]; then
        # 获取合并请求详细信息
        merge_request_info=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
          "$GITLAB_INTERNAL_URL/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID")

        # 获取 author 的用户名并查询邮箱
        author_username=$(echo "$merge_request_info" | jq -r '.author.username')
        RECIPIENT_EMAIL=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
          "$GITLAB_INTERNAL_URL/api/v4/users?username=$author_username" | jq -r '.[0].email')

        # 获取 assignees 和 reviewers 的用户名列表
        assignees_usernames=$(echo "$merge_request_info" | jq -r '.assignees[].username')
        reviewers_usernames=$(echo "$merge_request_info" | jq -r '.reviewers[].username')

        # 初始化抄送人邮箱
        CC_EMAILS=""

        # 获取 assignees 的邮箱
        for username in $assignees_usernames; do
          email=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
            "$GITLAB_INTERNAL_URL/api/v4/users?username=$username" | jq -r '.[0].email')
          CC_EMAILS+="$email,"
        done

        # 获取 reviewers 的邮箱
        for username in $reviewers_usernames; do
          email=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
            "$GITLAB_INTERNAL_URL/api/v4/users?username=$username" | jq -r '.[0].email')
          CC_EMAILS+="$email,"
        done

        # 去重并移除收件人邮箱
        if [[ -n "$RECIPIENT_EMAIL" && -n "$CC_EMAILS" ]]; then
          CC_EMAILS=$(echo "$CC_EMAILS" | sed "s/$RECIPIENT_EMAIL//g" | sed 's/,,/,/g' | sed 's/^,//;s/,$//')
        fi

        SOURCE_BRANCH=$(echo "$merge_request_info" | jq -r '.source_branch')
      else
        RECIPIENT_EMAIL=$(echo "$CI_COMMIT_AUTHOR" | sed -n 's/.*<\(.*\)>.*/\1/p')
        CC_EMAILS="$GLOBAL_CC_EMAILS"
        SOURCE_BRANCH="$CI_COMMIT_BRANCH"
        if [[ -n "$CC_EMAILS" ]]; then
          CC_EMAILS=$(echo "$CC_EMAILS" | sed "s/$RECIPIENT_EMAIL//g" | sed 's/,,/,/g' | sed 's/^,//;s/,$//')
        fi
      fi

      # 获取 SonarQube 质量门禁状态
      quality_gate_info=$(curl -s -u "$SONAR_TOKEN:" "$SONAR_HOST_URL/api/qualitygates/project_status?projectKey=$SONAR_PROJECT_KEY&branch=$SOURCE_BRANCH")
      status=$(echo "$quality_gate_info" | jq -r '.projectStatus.status')
      period_date=$(echo "$quality_gate_info" | jq -r '.projectStatus.period.date')

      # 准备邮件内容
      email_content="项目 '$SONAR_PROJECT_NAME' 分支 '$SOURCE_BRANCH' 的 SonarQube 扫描质量门禁状态: $status\n评估日期: $period_date\n\n详细信息:\n"
      conditions=$(echo "$quality_gate_info" | jq -c '.projectStatus.conditions[]')
      while read -r condition; do
        rule=$(echo "$condition" | jq -r '.metricKey')
        comparator=$(echo "$condition" | jq -r '.comparator')
        error_threshold=$(echo "$condition" | jq -r '.errorThreshold')
        actual_value=$(echo "$condition" | jq -r '.actualValue')
        condition_status=$(echo "$condition" | jq -r '.status')
        email_content+="指标: $rule, 比较符: $comparator, 错误阈值: $error_threshold, 实际值: $actual_value, 状态: $condition_status\n"
      done <<< "$conditions"

      sonar_project_url="$SONAR_PUBLIC_URL/dashboard?branch=$SOURCE_BRANCH&id=$SONAR_PROJECT_KEY"
      email_content+="\n SonarQube 项目地址: $sonar_project_url\n\n"

      # 如果质量门禁未通过,发送邮件
      if [ "$status" != "OK" ]; then
        echo "SonarQube质量门禁未通过,发送邮件通知..."

        # 生成收件人和抄送人 JSON 数组
        recipients=$(echo "$RECIPIENT_EMAIL" | jq -R -s 'split(",") | map({"email": (. | gsub("\n";""))})')
        if [ -n "$CC_EMAILS" ]; then
          cc_list=$(echo "$CC_EMAILS" | jq -R -s 'split(",") | map({"email": (. | gsub("\n";""))})')
          cc_field='"cc": '"$cc_list"','
        else
          cc_field=''
        fi

        # 调用 SendGrid API 发送邮件
        curl -s --request POST --url https://api.sendgrid.com/v3/mail/send \
          --header "Authorization: Bearer $SENDGRID_API_KEY" \
          --header 'Content-Type: application/json' \
          --data '{
            "personalizations": [
              {
                "to": '"$recipients"',
                '"$cc_field"'
                "subject": "SonarQube 扫描未通过通知"
              }
            ],
            "from": {"email": "gitlab@noreply.tomra.com"},
            "content": [
              {
                "type": "text/plain",
                "value": "'"$email_content"'"
              }
            ]
          }'
      fi
  dependencies:
    - sonarqube-scan
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: never # 主分支跳过
    - if: $CI_PIPELINE_SOURCE == "push"
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

4.项目.gitlab-ci.yml引用共享脚本

variables配置每个项目不同的扫描路径,或者其他不一样的sonarqube扫描参数

include:
  - project: 'fynn/devops'
    file: 'CI/ci_path_all.yml'
variables:
  SONAR_OPTIONS: >
    -Dsonar.sources=./app
    -Dsonar.inclusions=**/*
    -Dsonar.java.binaries=build

这样一次扫描脚本修改生效到所有项目,项目扫描变更更新自己的相关扫描配置就行

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注