使用代碼倉庫管理 GitLab CI 變量

本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或從新修改使用,但須要註明來源。 署名 4.0 國際 (CC BY 4.0)html

本文做者: 蘇洋node

建立時間: 2019年07月27日 統計字數: 6560字 閱讀時間: 14分鐘閱讀 本文連接: soulteary.com/2019/07/27/…ios


使用代碼倉庫管理 GitLab CI 變量

隨着愈來愈多的項目用上了自動化構建,咱們不得不在項目中一遍遍的配置持續集成中使用的環境變量,十幾個項目規模還好說,可是項目成百上千後,維護不一樣項目/不一樣項目分組變量的工做量也變的大了起來。git

在大公司中,若是有團隊維護基礎技術設施,咱們可使用相似可配置的構建平臺/應用配置中心等方案來解決這個問題。可是這類方案對於中小規模的團隊或者我的開發者來講卻不是那麼友好、甚至能夠說投入成本太高。程序員

本文將介紹如何使用代碼倉庫管理項目/項目組變量,低成本解決項目在CI/CD過程當中環境變量維護的問題。github

寫在前面

使用代碼倉庫管理應用文件配置你必定據說過或者用過,可是使用代碼倉庫管理環境變量,你或許就不必定用過了。docker

在聊具體方案以前,咱們先了解下這兩種配置的異同。它們的共同點是,都儲存了項目構建/運行所須要的必要信息。那麼他們主要的不一樣點是什麼呢?npm

CI/CD 變量和文件配置差別

  • 項目 CI/CD 變量:存放於 GitLab 項目/項目組設置頁面中變量配置中的字段、在 CI/CD 過程當中使用。
  • 項目配置文件:使用某種具體格式書寫,存放於項目倉庫某個位置,例如:./config/app.json 或者 ./app.ini等。在項目運行後使用。

簡單來講就是:存放位置不一樣、使用時機不一樣。編程

咱們都知道顯式聲明(Explicit declaration)對於維護性的利好,那麼若是咱們可以把變量也使用配置的方式來管理維護,問題就解決啦,好比像下面這樣使用:json

新的變量使用流程

  1. 讀取存放在文件中的變量信息
  2. 解析每一條配置
  3. 寫入 GitLab CI 變量配置

依賴條件

官方文檔 中有提到 Group-level Variables API,能夠對項目組的變量進行「CRUD」。(操做 Project-level Variables 同理)

使用有項目訪問權限的帳號,打開 https://gitlab.domain.com/profile/personal_access_tokens ,勾選 APIread_repository 權限,而後生成一枚相似 x6oeuvvfsoultearyZ2o 的 Access Token。

打開分組 CI 配置菜單

有了這枚 Token ,咱們就能模擬用戶對 GitLab 進行變量配置操做了。

獲取 Access-Token

編寫程序

相比較官方實例中的 Bash 語句,Node.js 等高級語言編寫的腳本能在完成相同事情的時候,行數更短,好比下面的60來行程序能夠解決這個問題:根據配置文件中定義的變量內容,設置多個項目或項目組、以及指定ID的項目或者項目組的變量配置。

const https = require('https');
const axios = require('axios');
const instance = axios.create({ httpsAgent: new https.Agent({ rejectUnauthorized: false }) });

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

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

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

        const variablesKeyExists = variablesExists.map((variable) => variable.key);
        const newVariableKeys = Object.keys(varsData).filter((key) => !variablesKeyExists.includes(key));

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

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

        if (settings[`${type}Vars:${itemId}`]) {
            const itemData = combine(settings[`${type}Vars:${itemId}`]);
            delete settings[`${type}Vars:${itemId}`];
            update([itemId], itemData, type);
        }
    });
}

const groupsVarsList = combine(groupVars);
const projectVarsList = combine(projectVars);

update(groupIds, groupsVarsList, 'group');
update(projectIds, projectVarsList, 'project');
複製代碼

想要使用這段腳本,須要建立一個配置文件 setting.json,將上面獲取的 Token、你的 GitLab 倉庫地址,以及你想配置的項目或項目組 id 放進去,一個相對完整的例子是下面這樣。

{
    "baseHost": "https://gitlab.lab.com/api/v4",
    "token": "H7hHmdCnryy7UCeFB7tH",
    "groupIds": [
        5
    ],
    "groupVars": {
        "publicVars": {
            "VAR_PUB": 1024
        },
        "privateVars": {
            "VAR_HIDE": 1024,
            "VAR_HIDE2": 1024
        }
    },
    "projectIds": [
        774,
        775
    ],
    "projectVars": {
        "publicVars": {
            "VAR_PUB": 1024
        },
        "privateVars": {
            "VAR_HIDE": 1024,
            "VAR_HIDE2": 1024
        }
    },
    "projectVars:775": {
        "publicVars": {
            "VAR_775_PUB": 2048
        },
        "privateVars": {
            "VAR_775_HIDE": 2048,
            "VAR_775_HIDE2": 2048
        }
    }
}
複製代碼

這裏除了 baseHosttoken 字段外,其餘的配置都是選配的,包括二級字段 publicVarsprivateVars,因此若是你只是想配置兩個項目組,的公開變量,配置會簡短很多。

{
    "baseHost": "https://gitlab.lab.com/api/v4",
    "token": "H7hHmdCnryy7UCeFB7tH",
    "groupIds": [
        5, 6
    ],
    "groupVars": {
        "publicVars": {
            "VAR_PUB": 1024
        }
    }
}
複製代碼

考慮到不是每一個同窗都熟悉 JavaScript,我這裏把它封裝成了容器鏡像。

構建工具容器鏡像

鏡像文件十分簡單,基於 Node 官方鏡像,10 行之內指令解決問題。

FROM node:12.7.0-alpine

LABEL MAINTAINER="soulteary"

COPY ./src /app

WORKDIR /app

RUN npm i --production

VOLUME [ "/app/settings.json" ]

ENTRYPOINT [ "npm", "start" ]
複製代碼

將上面的內容保存爲 Dockerfile ,接着使用 docker build 構建一個咱們使用的工具鏡像:

docker build -t soulteary/gitlab-variable-helper .
複製代碼

固然,你也能夠直接使用我在 DockerHub 上提供的公開鏡像:soulteary/gitlab-variable-helper

如何使用

在準備好你的配置文件 settings.json 後,你能夠在本地環境或者服務器、或是 GitLab Runner 中執行這個工具。

執行方法除了安裝好 Node.js 後執行 node .npm start 外,還能夠選擇使用容器來執行:

docker run --rm -v `pwd`/settings.json:/app/settings.json soulteary/gitlab-variable-helper:1.0.0
複製代碼

固然,我更推薦的是使用 compose 文件進行容器執行,由於看起來會更加的清晰。

version: '3'

services:

  updater:
    image: docker.lab.com/gitlab-group-variable-update:1.0.0
    volumes:
     - ./config:/app/config
複製代碼

將上面的文件保存爲 docker-compose.yml 後,咱們能夠再編寫一個 .gitlab-ci.yml ,讓變量配置變的「自動」起來:

stages:
  - deploy

update:
  stage: deploy
  script:
    - docker-compose down && docker-compose up
    # 或者
    # - docker run --rm -v `pwd`/settings.json:/app/settings.json soulteary/gitlab-variable-helper:1.0.0
複製代碼

若是你CI配置正確,每當你調整 settings.json內容,並使用 git push 將內容提交到 GitLab 後,都將會看到相似下面的日誌輸出。

變量配置成功

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

設置變量完畢

打開項目/項目組頁面,能夠看到變量已經被成功設置完畢了。

完整項目,我已經提交到 GitHub 了:github.com/soulteary/g…,感興趣的同窗能夠自取。

最後

懶是程序員的美德,爲了能變懶去折騰也是。

—EOF


我如今有一個小小的折騰羣,裏面彙集了一些喜歡折騰的小夥伴。

在不發廣告的狀況下,咱們在裏面會一塊兒聊聊軟件、HomeLab、編程上的一些問題,也會在羣裏不按期的分享一些技術沙龍的資料。

喜歡折騰的小夥伴歡迎掃碼添加好友。(請註明來源和目的,不然不會經過審覈)

關於折騰羣入羣的那些事

相關文章
相關標籤/搜索