使用代码仓库管理 GitLab CI 变量

论坛 期权论坛 期权     
为了不折腾而去折腾的那些事   2019-7-27 22:50   4004   0
本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)
本文作者: 苏洋
创建时间: 2019年07月27日 统计字数: 6560字 阅读时间: 14分钟阅读 本文链接: https://soulteary.com/2019/07/27/use-the-code-repository-to-manage-gitlab-ci-variables.html
使用代码仓库管理 GitLab CI 变量随着越来越多的项目用上了自动化构建,我们不得不在项目中一遍遍的配置持续集成中使用的环境变量,十几个项目规模还好说,但是项目成百上千后,维护不同项目/不同项目分组变量的工作量也变的大了起来。
在大公司中,如果有团队维护基础技术设施,我们可以使用类似可配置的构建平台/应用配置中心等方案来解决这个问题。但是这类方案对于中小规模的团队或者个人开发者来说却不是那么友好、甚至可以说投入成本过高。
本文将介绍如何使用代码仓库管理项目/项目组变量,低成本解决项目在CI/CD过程中环境变量维护的问题。
[h1]写在前面[/h1]使用代码仓库管理应用文件配置你一定听说过或者用过,但是使用代码仓库管理环境变量,你或许就不一定用过了。
在聊具体方案之前,我们先了解下这两种配置的异同。它们的共同点是,都储存了项目构建/运行所需要的必要信息。那么他们主要的不同点是什么呢?


  • 项目 CI/CD 变量:存放于 GitLab 项目/项目组设置页面中变量配置中的字段、在 CI/CD 过程中使用。
  • 项目配置文件:使用某种具体格式书写,存放于项目仓库某个位置,例如:
    1. ./config/app.json
    复制代码
    或者
    1. ./app.ini
    复制代码
    等。在项目运行后使用。
简单来说就是:存放位置不同、使用时机不同。
我们都知道显式声明(Explicit declaration)对于维护性的利好,那么如果我们能够把变量也使用配置的方式来管理维护,问题就解决啦,比如像下面这样使用:


  • 读取存放在文件中的变量信息
  • 解析每一条配置
  • 写入 GitLab CI 变量配置
[h1]依赖条件[/h1]官方文档 中有提到
  1. Group-levelVariablesAPI
复制代码
,可以对项目组的变量进行“CRUD”。(操作 Project-level Variables 同理)
使用有项目访问权限的账号,打开
  1. https://gitlab.domain.com/profile/personal_access_tokens
复制代码
,勾选
  1. API
复制代码
  1. read_repository
复制代码
权限,然后生成一枚类似
  1. x6oeuvvfsoultearyZ2o
复制代码
的 Access Token。


有了这枚 Token ,我们就能模拟用户对 GitLab 进行变量配置操作了。


[h1]编写程序[/h1]相比较官方实例中的
  1. Bash
复制代码
语句,
  1. Node.js
复制代码
等高级语言编写的脚本能在完成相同事情的时候,行数更短,比如下面的60来行程序可以解决这个问题:根据配置文件中定义的变量内容,设置多个项目或项目组、以及指定ID的项目或者项目组的变量配置。
    1. const https = require('https');
    复制代码
    1. const axios = require('axios');
    复制代码
    1. const instance = axios.create({ httpsAgent: new https.Agent({ rejectUnauthorized: false }) });
    复制代码

    1. /**
    复制代码
    1. * settings @see `setting.example.json`
    复制代码
    1. */
    复制代码
    1. const settings = require('./settings.json');
    复制代码
    1. const { baseHost, token, groupIds, projectIds, groupVars, projectVars } = settings;
    复制代码
    1. const options = { headers: { 'PRIVATE-TOKEN': token } };
    复制代码

    1. function combine(varList) {
    复制代码
    1.     if (!varList) return {};
    复制代码
    1.     const { privateVars, publicVars } = varList;
    复制代码
    1.     return Object.keys(publicVars).map((label) => { return { label, value: publicVars[label], protected: false } })
    复制代码
    1.         .concat(Object.keys(privateVars).map((label) => { return { label, value: privateVars[label], protected: true } }))
    复制代码
    1.         .reduce((r, i) => { r[i.label] = i; return r; }, {});
    复制代码
    1. }
    复制代码

    1. function update(itemIds, varsData, type) {
    复制代码
    1.     type = type.slice(-1) === 's' ? type.slice(0, -1) : type;
    复制代码
    1.     const apiType = `${type}s`;
    复制代码
    1.     itemIds.forEach(async (itemId) => {
    复制代码
    1.         try {
    复制代码
    1.             var { data: variablesExists } = await instance.get(`${baseHost}/${apiType}/${itemId}/variables`, options);
    复制代码
    1.         } catch (error) {
    复制代码
    1.             return console.log(error);
    复制代码
    1.         }
    复制代码

    1.         const variablesKeyExists = variablesExists.map((variable) => variable.key);
    复制代码
    1.         const newVariableKeys = Object.keys(varsData).filter((key) => !variablesKeyExists.includes(key));
    复制代码

    1.         variablesKeyExists.forEach(async (key) => {
    复制代码
    1.             if (!varsData[key]) return;
    复制代码
    1.             const { value, protected } = varsData[key];
    复制代码
    1.             try {
    复制代码
    1.                 await instance.put(`${baseHost}/${apiType}/${itemId}/variables/${key}`, `value=${value}&protected=${protected}`, options);
    复制代码
    1.             } catch (error) {
    复制代码
    1.                 return console.log(error);
    复制代码
    1.             }
    复制代码
    1.             console.log(`Update #${itemId} ${type}: [${key}]`);
    复制代码
    1.         });
    复制代码

    1.         newVariableKeys.forEach(async (key) => {
    复制代码
    1.             if (!varsData[key]) return;
    复制代码
    1.             const { value, protected } = varsData[key];
    复制代码
    1.             try {
    复制代码
    1.                 await instance.post(`${baseHost}/${apiType}/${itemId}/variables`, `key=${key}&value=${value}&protected=${protected}`, options);
    复制代码
    1.             } catch (error) {
    复制代码
    1.                 return console.log(error);
    复制代码
    1.             }
    复制代码
    1.             console.log(`Create #${itemId} ${apiType}: [${key}]`);
    复制代码
    1.         });
    复制代码

    1.         if (settings[`${type}Vars:${itemId}`]) {
    复制代码
    1.             const itemData = combine(settings[`${type}Vars:${itemId}`]);
    复制代码
    1.             delete settings[`${type}Vars:${itemId}`];
    复制代码
    1.             update([itemId], itemData, type);
    复制代码
    1.         }
    复制代码
    1.     });
    复制代码
    1. }
    复制代码

    1. const groupsVarsList = combine(groupVars);
    复制代码
    1. const projectVarsList = combine(projectVars);
    复制代码

    1. update(groupIds, groupsVarsList, 'group');
    复制代码
    1. update(projectIds, projectVarsList, 'project');
    复制代码
想要使用这段脚本,需要创建一个配置文件
  1. setting.json
复制代码
,将上面获取的 Token、你的 GitLab 仓库地址,以及你想配置的项目或项目组 id 放进去,一个相对完整的例子是下面这样。
    1. {
    复制代码
    1.     "baseHost": "https://gitlab.lab.com/api/v4",
    复制代码
    1.     "token": "H7hHmdCnryy7UCeFB7tH",
    复制代码
    1.     "groupIds": [
    复制代码
    1.         5
    复制代码
    1.     ],
    复制代码
    1.     "groupVars": {
    复制代码
    1.         "publicVars": {
    复制代码
    1.             "VAR_PUB": 1024
    复制代码
    1.         },
    复制代码
    1.         "privateVars": {
    复制代码
    1.             "VAR_HIDE": 1024,
    复制代码
    1.             "VAR_HIDE2": 1024
    复制代码
    1.         }
    复制代码
    1.     },
    复制代码
    1.     "projectIds": [
    复制代码
    1.         774,
    复制代码
    1.         775
    复制代码
    1.     ],
    复制代码
    1.     "projectVars": {
    复制代码
    1.         "publicVars": {
    复制代码
    1.             "VAR_PUB": 1024
    复制代码
    1.         },
    复制代码
    1.         "privateVars": {
    复制代码
    1.             "VAR_HIDE": 1024,
    复制代码
    1.             "VAR_HIDE2": 1024
    复制代码
    1.         }
    复制代码
    1.     },
    复制代码
    1.     "projectVars:775": {
    复制代码
    1.         "publicVars": {
    复制代码
    1.             "VAR_775_PUB": 2048
    复制代码
    1.         },
    复制代码
    1.         "privateVars": {
    复制代码
    1.             "VAR_775_HIDE": 2048,
    复制代码
    1.             "VAR_775_HIDE2": 2048
    复制代码
    1.         }
    复制代码
    1.     }
    复制代码
    1. }
    复制代码
这里除了
  1. baseHost
复制代码
  1. token
复制代码
字段外,其他的配置都是选配的,包括二级字段
  1. publicVars
复制代码
  1. privateVars
复制代码
,所以如果你只是想配置两个项目组,的公开变量,配置会简短不少。
    1. {
    复制代码
    1.     "baseHost": "https://gitlab.lab.com/api/v4",
    复制代码
    1.     "token": "H7hHmdCnryy7UCeFB7tH",
    复制代码
    1.     "groupIds": [
    复制代码
    1.         5, 6
    复制代码
    1.     ],
    复制代码
    1.     "groupVars": {
    复制代码
    1.         "publicVars": {
    复制代码
    1.             "VAR_PUB": 1024
    复制代码
    1.         }
    复制代码
    1.     }
    复制代码
    1. }
    复制代码
考虑到不是每个同学都熟悉 JavaScript,我这里把它封装成了容器镜像。
[h1]构建工具容器镜像[/h1]镜像文件十分简单,基于 Node 官方镜像,10 行以内指令解决问题。
    1. FROM node:12.7.0-alpine
    复制代码

    1. LABEL MAINTAINER="soulteary"
    复制代码

    1. COPY ./src /app
    复制代码

    1. WORKDIR /app
    复制代码

    1. RUN npm i --production
    复制代码

    1. VOLUME [ "/app/settings.json" ]
    复制代码

    1. ENTRYPOINT [ "npm", "start" ]
    复制代码
将上面的内容保存为
  1. Dockerfile
复制代码
,接着使用
  1. docker build
复制代码
构建一个我们使用的工具镜像:
    1. docker build -t soulteary/gitlab-variable-helper .
    复制代码
当然,你也可以直接使用我在 DockerHub 上提供的公开镜像:soulteary/gitlab-variable-helper 。
[h1]如何使用[/h1]在准备好你的配置文件
  1. settings.json
复制代码
后,你可以在本地环境或者服务器、或是 GitLab Runner 中执行这个工具。
执行方法除了安装好 Node.js 后执行
  1. node.
复制代码
  1. npm start
复制代码
外,还可以选择使用容器来执行:
    1. docker run --rm -v `pwd`/settings.json:/app/settings.json soulteary/gitlab-variable-helper:1.0.0
    复制代码
当然,我更推荐的是使用
  1. compose
复制代码
文件进行容器执行,因为看起来会更加的清晰。
    1. version: '3'
    复制代码

    1. services:
    复制代码

    1.   updater:
    复制代码
    1.     image: docker.lab.com/gitlab-group-variable-update:1.0.0
    复制代码
    1.     volumes:
    复制代码
    1.      - ./config:/app/config
    复制代码
将上面的文件保存为
  1. docker-compose.yml
复制代码
后,我们可以再编写一个
  1. .gitlab-ci.yml
复制代码
,让变量配置变的“自动”起来:
    1. stages:
    复制代码
    1.   - deploy
    复制代码

    1. update:
    复制代码
    1.   stage: deploy
    复制代码
    1.   script:
    复制代码
    1.     - docker-compose down && docker-compose up
    复制代码
    1.     # 或者
    复制代码
    1.     # - docker run --rm -v `pwd`/settings.json:/app/settings.json soulteary/gitlab-variable-helper:1.0.0
    复制代码
如果你CI配置正确,每当你调整
  1. settings.json
复制代码
内容,并使用
  1. git push
复制代码
将内容提交到 GitLab 后,都将会看到类似下面的日志输出。


    1. docker-compose down && docker-compose up
    复制代码
    1. Creating gitlab-group-update-test_updater_1 ... done
    复制代码
    1. Attaching to gitlab-group-update-test_updater_1
    复制代码
    1. updater_1  |
    复制代码
    1. updater_1  | > gitlab-group-variable-helper@1.0.0 start /app
    复制代码
    1. updater_1  | > node .
    复制代码
    1. updater_1  |
    复制代码
    1. updater_1  | Create #774 project: [VAR_PUB]
    复制代码
    1. updater_1  | Update #775 project: [VAR_HIDE2]
    复制代码
    1. updater_1  | Create #5 group: [VAR_PUB]
    复制代码
    1. updater_1  | Update #5 group: [VAR_HIDE]
    复制代码
    1. updater_1  | Create #775 project: [VAR_HIDE]
    复制代码
    1. updater_1  | Create #774 project: [VAR_HIDE]
    复制代码
    1. updater_1  | Update #5 group: [VAR_HIDE2]
    复制代码
    1. updater_1  | Update #775 project: [VAR_PUB]
    复制代码
    1. updater_1  | Update #774 project: [VAR_HIDE2]
    复制代码
    1. updater_1  | Update #775 project: [VAR_775_HIDE2]
    复制代码
    1. updater_1  | Update #775 project: [VAR_775_PUB]
    复制代码
    1. updater_1  | Update #775 project: [VAR_775_HIDE]
    复制代码
    1. gitlab-group-update-test_updater_1 exited with code 0
    复制代码

打开项目/项目组页面,可以看到变量已经被成功设置完毕了。
完整项目,我已经提交到 GitHub 了:https://github.com/soulteary/gitlab-variable-helper,感兴趣的同学可以自取。
[h1]最后[/h1]懒是程序员的美德,为了能变懒去折腾也是。
—EOF

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP