我们在Braintree的许多新应用程序使用环境变量进行配置。这符合Twelve-Factor原则,并使在多个环境中运行同一工件更加容易。在我们的案例中,工件是一个Docker镜像。Docker原生支持env文件,所以我们可以将配置写入文件,然后使用docker run --env-file .env ... 。
在本地开发中,我们使用.env 文件进行配置,一般使用docker-compose,它也支持这个功能。
# docker-compose.yml
myapp:
image: myapp
ports:
- "8080:8080"
env_file: .env
然而,我们的构建工具Bazel并不支持这个功能。相反,你可以在命令行中传递单个环境变量。
bazel test --test_env EMAIL=a@example.com --test_env MODE=dev //...
我在GitHub上开了一个关于这个功能的问题(github.com/bazelbuild/…
使用方法
我们决定把我们常用的bazel调用包装成一个脚本,知道如何处理我们的.env 文件。
下面是它的用法。
./bazel build
./bazel build //myapp
./bazel test
./bazel test //myapp:test
./bazel run //myapp
构建和测试命令允许目标,如果没有通过,则假定你想要的一切(//...)。Bazel也没有办法为运行命令传入环境变量,所以在这种情况下,我们可以在运行目标之前对现有的.env 文件进行源化。
我们的脚本
下面是这个脚本的样子(为了清晰起见,略作删减)。
#!/usr/bin/env ruby
ENV_FILE = ".env"
def main
usage unless ARGV.size > 0
case ARGV.first
when "build"
build(ARGV[1..-1])
when "test"
test(ARGV[1..-1])
when "run"
run(ARGV[1..-1])
else
usage
end
end
def usage
puts "Usage: ./bazel (build|test|run) <args>"
exit 1
end
def build(targets)
puts_and_exec "bazel build #{build_targets(targets)}"
end
def test(targets)
test_envs = env_variables.map { |e| "--test_env #{e}"}.join(" ")
puts_and_exec "bazel test #{test_envs} #{build_targets(targets)}"
end
def run(args)
puts_and_exec "bash -c 'set -a; source #{ENV_FILE}; bazel run #{args.join(" ")}'"
end
def env_variables
File.readlines(ENV_FILE).map do |line|
key, value = line.strip.split("=", 2)
"#{key}=#{ENV[key] || value}"
end
end
def build_targets(targets)
targets.empty? ? "//..." : targets.join(" ")
end
def puts_and_exec(command)
puts command
exec command
end
main if __FILE__ == $0
"#{key}=#{ENV[key] || value}" 部分允许环境覆盖.env 文件中的值。这让我们可以做这样的事情。
FOO=bar ./bazel run ...