🏭

CodeBuildのローカルビルド環境変数展開: eval不使用版

2024/02/16に公開

下記記事の続き: eval不使用版

TL;DR

  • 環境変数ファイルをマウント&読み込みさせたコンテナを起動し、キーと値のペアをbase64エンコード
  • local_build.shでbase64デコードした環境変数をファイルに書き出してcustomer-specific.ymlへ追記する

イメージ図

事前準備

  • 環境変数エンコード環境作成

    • エンコードスクリプト
    parse_envs.py
    import os
    from base64 import b64encode
    from pathlib import Path
    
    envs: dict[str, str] = {}
    [
        [
            envs.update({line: os.environ.get(line, "")})
            for line in file.read_text(encoding="utf-8").split("\n")
            if line in os.environ.keys()
        ]
        for file in Path("/envs").glob("**/*.env")
    ]
    
    (
        print(
            b64encode(
                "\n".join([f"{key}={value}" for key, value in envs.items()]).encode()
            ).decode()
        )
        if 0 < len(envs.keys())
        else None
    )
    
  • コンテナイメージ用Dockerfile

    Dockerfile
    FROM python:3.13.0a3-slim-bookworm
    
    COPY ./parse_envs.py /
    
    ENTRYPOINT [ "/usr/local/bin/python" ]
    
    CMD [ "/parse_envs.py" ]
    
  • イメージ作成

    docker build -t parse-envs:latest .
    
  • 冒頭記事のmy-local-builds:latestまでは作成しておく

スクリプト修正

  • codebuild_build.shの修正
    冒頭記事の同ファイルの修正はrevertしておく。
codebuild_build.sh
@@ -46,6 +46,8 @@ function usage {
     echo "  -m        Used to mount the source directory to the customer build container directly."
     echo "  -d        Used to run the build container in docker privileged mode."
     echo "  -e FILE   Used to specify a file containing environment variables."
+    echo "  -n FILE   Used to specify a file containing names of environment variable for expansion."
+    echo "  -x FILE   Used to specify a file containing environment variables source for expansion."
     echo "            (-e) File format expectations:"
     echo "               * Each line is in VAR=VAL format"
     echo "               * Lines beginning with # are processed as comments and ignored"
@@ -61,7 +63,7 @@ awsconfig_flag=false
 mount_src_dir_flag=false
 docker_privileged_mode_flag=false
 
-while getopts "cmdi:a:r:s:b:e:l:p:h" opt; do
+while getopts "cmdi:a:r:s:b:e:n:x:l:p:h" opt; do
     case $opt in
         i  ) image_flag=true; image_name=$OPTARG;;
         a  ) artifact_flag=true; artifact_dir=$OPTARG;;
@@ -72,6 +74,8 @@ while getopts "cmdi:a:r:s:b:e:l:p:h" opt; do
         d  ) docker_privileged_mode_flag=true;;
         s  ) source_dirs+=("$OPTARG");;
         e  ) environment_variable_file=$OPTARG;;
+        n  ) names_of_expand_environment_variable_file=$OPTARG;;
+        x  ) expand_environment_variable_file=$OPTARG;;
         l  ) local_agent_image=$OPTARG;;
         p  ) aws_profile=$OPTARG;;
         h  ) usage; exit;;
@@ -182,6 +186,22 @@ else
     docker_command+=" -e \"INITIATOR=$USER\""
 fi
 
+if [ -f "$names_of_expand_environment_variable_file" ]
+then
+    customer_envs="$(bash -c "if [ -f "$expand_environment_variable_file" ]; \
+        then source $expand_environment_variable_file; \
+        fi && \
+        docker run \
+        --rm \
+        --env-file=${names_of_expand_environment_variable_file} \
+        -v ${names_of_expand_environment_variable_file}:/envs/.env \
+        parse-envs:latest")"
+    if [ -n "$customer_envs" ]
+    then
+        docker_command+=" -e CUSTOMER_ENVIRONMENTS=\"${customer_envs}\""
+    fi
+fi
+
 if [ -n "$local_agent_image" ]
 then
     docker_command+=" $local_agent_image

実行方法

ビルドコンテナに渡したい環境変数名リストファイルをnames.env、変数値の上書き・展開が必要な場合の変数値リストファイルをvalues.env(オプション)とする。
変数値リストファイルはexportをつける。

names.env
ENV1
ENV2
values.env
export ENV1=$(pwd)
export ENV2="pwd: ${ENV2}"

codebuild_build.shを以下のように実行する。

command
./codebuild_build.sh \
  -i public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:5.0-24.02.08 \
  -s ./src \
  -a ./artifacts \
  -n ./names.env
  -x values.env

※-nは相対パスの場合./をつけないと正しくマウントできない。

実行結果

agent_1  | [Container] ****/**/** **:**:** Running command echo ${ENV1}
agent_1  | /root/local_builds
agent_1  | 
agent_1  | [Container] ****/**/** **:**:** Running command echo ${ENV2}
agent_1  | pwd: /root/local_builds
agent_1  | 
agent_1  | [Container] ****/**/** **:**:** Phase complete: BUILD State: SUCCEEDED
詳細

codebuild_build.sh内で環境変数を評価するためevalを実行することは実行中シェルの環境を変更させてしまうため安全とは言えない。
そこで別シェルで環境変数を読み込み、agentに環境変数値を渡すためにコンテナを用いて環境変数のペアをbase64でエンコードする。(シェル側で解析すればシェル内で完結させることも不可能ではないが、自前の解析処理を記述するよりもpythonの標準ライブラリを用いたほうが間違いもなく処理はシンプルにもなるためpythonコンテナ内で処理させている)

このようにすることで、ホスト側の環境から完全に隔離された状態で環境変数を展開した結果を取得することができる。
構造としては複雑であまりスマートとは言えないが、確実かつ安全に処理することはできているはずである。

codebuild_build.sh
customer_envs="$(bash -c "if [ -f "$expand_environment_variable_file" ]; \
    then source $expand_environment_variable_file; \
    fi && \
    docker run \
    --rm \
    --env-file=${names_of_expand_environment_variable_file} \
    -v ${names_of_expand_environment_variable_file}:/envs/.env \
    parse-envs:latest")"
parse_envs.py
[
  [
      envs.update({line: os.environ.get(line, "")})
      for line in file.read_text(encoding="utf-8").split("\n")
      if line in os.environ.keys()
  ]
  for file in Path("/envs").glob("**/*.env")
]
・・・
b64encode(
  "\n".join([f"{key}={value}" for key, value in envs.items()]).encode()
).decode()

local_build.shでのデコードは冒頭記事と同じ仕組みを使用。

Discussion