2 # Cookbook Name:: concourse-ci
3 # Recipe:: docker-compose
5 # Copyright 2017-2018, whitestar
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
20 require 'securerandom'
22 doc_url = 'https://concourse.ci/docker-repository.html'
24 include_recipe 'platform_utils::kernel_user_namespace'
25 include_recipe 'docker-grid::compose'
27 app_dir = node['concourse-ci']['docker-compose']['app_dir']
28 bin_dir = "#{app_dir}/bin"
29 pgdata_dir = node['concourse-ci']['docker-compose']['pgdata_dir']
30 web_keys_dir = node['concourse-ci']['docker-compose']['web_keys_dir']
31 worker_keys_dir = node['concourse-ci']['docker-compose']['worker_keys_dir']
39 resources(directory: dir) rescue directory dir do
48 resources(directory: pgdata_dir) rescue directory pgdata_dir do
53 end if !pgdata_dir.nil? && !pgdata_dir.empty?
55 bash 'ssh-keygen_keys' do
57 rm -f #{web_keys_dir}/*
58 ssh-keygen -t rsa -f #{web_keys_dir}/tsa_host_key -N ''
59 ssh-keygen -t rsa -f #{web_keys_dir}/session_signing_key -N ''
60 rm -f #{worker_keys_dir}/*
61 ssh-keygen -t rsa -f #{worker_keys_dir}/worker_key -N ''
62 cp #{worker_keys_dir}/worker_key.pub #{web_keys_dir}/authorized_worker_keys
63 cp #{web_keys_dir}/tsa_host_key.pub #{worker_keys_dir}
66 unless node['concourse-ci']['docker-compose']['ssh_keys_reset']
67 not_if { File.exist?("#{web_keys_dir}/tsa_host_key") }
68 not_if { File.exist?("#{web_keys_dir}/session_signing_key") }
69 not_if { File.exist?("#{worker_keys_dir}/worker_key") }
73 env_file = "#{app_dir}/.env"
74 config_file = "#{app_dir}/docker-compose.yml"
77 if File.exist?(env_file)
79 File.open(env_file) do |file|
80 file.each_line do |line|
81 env_local[$1] = $2 if line =~ /^([^=]*)=(.*)$/
86 config_srvs_local = nil
87 if File.exist?(config_file)
89 config_srvs_local = YAML.load_file(config_file)
90 config_srvs_local = config_srvs_local['services'] if config_srvs_local.key?('version') && config_srvs_local['version'].to_i >= 2
93 config_format_version = node['concourse-ci']['docker-compose']['config_format_version']
95 # if config_format_version.to_i == 1
96 config_srvs = node['concourse-ci']['docker-compose']['config']
97 override_config_srvs = node.override['concourse-ci']['docker-compose']['config']
98 force_override_config_srvs = node.force_override['concourse-ci']['docker-compose']['config']
99 if config_format_version.to_i == 2
100 config_srvs = config_srvs['services']
101 override_config_srvs = override_config_srvs['services']
102 force_override_config_srvs = force_override_config_srvs['services']
106 db_envs_org = config_srvs['concourse-db']['environment']
108 db_vols = config_srvs['concourse-db']['volumes'].to_a
110 db_password_reset = node['concourse-ci']['docker-compose']['db_password_reset']
112 db_password_vault_item = node['concourse-ci']['docker-compose']['db_password_vault_item']
113 if !db_password_vault_item.empty?
114 # 1. from Chef Vault (recommended).
115 db_passwd = get_vault_item_value(db_password_vault_item)
117 # 2. from Chef attribute (NOT recommended).
118 db_passwd = db_envs_org['POSTGRES_PASSWORD']
119 if db_passwd.nil? || db_passwd.empty?
121 if !config_srvs_local.nil? \
122 && config_srvs_local['concourse-db']['environment']['POSTGRES_PASSWORD'] != '${POSTGRES_PASSWORD}' \
123 && !db_password_reset
124 # 3. preserve it from the local docker-compose.yml file for backward compatibility.
125 config_srvs_local['concourse-db']['environment']['POSTGRES_PASSWORD']
126 elsif !env_local.nil? && !env_local['POSTGRES_PASSWORD'].nil? && !db_password_reset
127 # 4. preserve it from the local .env file.
128 env_local['POSTGRES_PASSWORD']
131 SecureRandom.hex # or urlsafe_base64
135 # prevent Chef from logging password attribute value. (=> template variables)
136 db_envs['POSTGRES_PASSWORD'] = '${POSTGRES_PASSWORD}'
138 db_vols.push("#{pgdata_dir}:#{db_envs_org['PGDATA']}") if !pgdata_dir.nil? && !pgdata_dir.empty?
140 # merge environment hash
141 force_override_config_srvs['concourse-db']['environment'] = db_envs unless db_envs.empty?
142 # reset vlumes array.
143 override_config_srvs['concourse-db']['volumes'] = db_vols unless db_vols.empty?
146 web_envs_org = config_srvs['concourse-web']['environment']
148 web_vols = config_srvs['concourse-web']['volumes'].to_a
150 web_ports = config_srvs['concourse-web']['ports']
151 override_config_srvs['concourse-web']['ports'] = ['8080:8080'] if web_ports.empty?
153 web_vols.push("#{node['concourse-ci']['docker-compose']['web_keys_dir']}:/concourse-keys")
156 encryption_key_vault_item = node['concourse-ci']['docker-compose']['web_encryption_key_vault_item']
157 unless encryption_key_vault_item.empty?
158 encryption_key = get_vault_item_value(encryption_key_vault_item)
159 web_envs['CONCOURSE_ENCRYPTION_KEY'] = '${CONCOURSE_ENCRYPTION_KEY}'
162 web_password_reset = node['concourse-ci']['docker-compose']['web_password_reset']
163 basic_auth_passwd = nil
164 web_password_vault_item = node['concourse-ci']['docker-compose']['web_password_vault_item']
165 if !web_password_vault_item.empty?
166 # 1. from Chef Vault (recommended).
167 basic_auth_passwd = get_vault_item_value(web_password_vault_item)
169 # 2. from Chef attribute (NOT recommended).
170 basic_auth_passwd = web_envs_org['CONCOURSE_BASIC_AUTH_PASSWORD']
171 if basic_auth_passwd.nil? || basic_auth_passwd.empty?
172 basic_auth_passwd = \
173 if !config_srvs_local.nil? \
174 && config_srvs_local['concourse-web']['environment']['CONCOURSE_BASIC_AUTH_PASSWORD'] != '${CONCOURSE_BASIC_AUTH_PASSWORD}' \
175 && !web_password_reset
176 # 3. preserve it from the local docker-compose.yml file for backward compatibility.
177 config_srvs_local['concourse-web']['environment']['CONCOURSE_BASIC_AUTH_PASSWORD']
178 elsif !env_local.nil? && !env_local['CONCOURSE_BASIC_AUTH_PASSWORD'].nil? && !web_password_reset
179 # 4. preserve it from the local .env file.
180 env_local['CONCOURSE_BASIC_AUTH_PASSWORD']
183 SecureRandom.hex # or urlsafe_base64
187 # prevent Chef from logging password attribute value. (=> template variables)
188 web_envs['CONCOURSE_BASIC_AUTH_PASSWORD'] = '${CONCOURSE_BASIC_AUTH_PASSWORD}'
190 oauth_client_id = nil
191 oauth_client_id_vault_item = node['concourse-ci']['docker-compose']['web_oauth_client_id_vault_item']
192 unless oauth_client_id_vault_item.empty?
193 oauth_client_id = get_vault_item_value(oauth_client_id_vault_item)
194 web_envs['CONCOURSE_GENERIC_OAUTH_CLIENT_ID'] = '${CONCOURSE_GENERIC_OAUTH_CLIENT_ID}'
197 oauth_client_secret = nil
198 oauth_client_secret_vault_item = node['concourse-ci']['docker-compose']['web_oauth_client_secret_vault_item']
199 unless oauth_client_secret_vault_item.empty?
200 oauth_client_secret = get_vault_item_value(oauth_client_secret_vault_item)
201 web_envs['CONCOURSE_GENERIC_OAUTH_CLIENT_SECRET'] = '${CONCOURSE_GENERIC_OAUTH_CLIENT_SECRET}'
204 external_url = web_envs_org['CONCOURSE_EXTERNAL_URL']
205 external_url = "http://#{node['ipaddress']}:8080" if external_url.nil?
206 web_envs['CONCOURSE_EXTERNAL_URL'] = external_url
208 data_source = web_envs_org['CONCOURSE_POSTGRES_DATA_SOURCE']
209 # for backward compatibility.
210 data_source = data_source.gsub(/<POSTGRES_PASSWORD>/, '${POSTGRES_PASSWORD}')
211 web_envs['CONCOURSE_POSTGRES_DATA_SOURCE'] = data_source
213 template "#{bin_dir}/concourse_up" do
214 source 'opt/docker-compose/app/concourse/bin/concourse_up'
221 if node['concourse-ci']['with_ssl_cert_cookbook']
222 ::Chef::Recipe.send(:include, SSLCert::Helper)
223 cn = node['concourse-ci']['ssl_cert']['common_name']
224 append_server_ssl_cn(cn)
225 include_recipe 'ssl_cert::server_key_pairs'
227 # Concourse web process owner is root.
228 web_vols.push("#{server_cert_path(cn)}:/root/server.crt:ro")
229 web_vols.push("#{server_key_path(cn)}:/root/server.key:ro")
230 web_envs['CONCOURSE_TLS_CERT'] = '/root/server.crt'
231 web_envs['CONCOURSE_TLS_KEY'] = '/root/server.key'
235 worker_vols = config_srvs['concourse-worker']['volumes'].to_a
236 worker_vols.push("#{node['concourse-ci']['docker-compose']['worker_keys_dir']}:/concourse-keys")
239 if node['concourse-ci']['docker-compose']['import_ca']
240 ::Chef::Recipe.send(:include, SSLCert::Helper)
242 node['concourse-ci']['ssl_cert']['ca_names'].each {|ca_name|
243 append_ca_name(ca_name)
244 ca_cert_vol = "#{ca_cert_path(ca_name)}:/usr/share/ca-certificates/#{ca_name}.crt:ro"
245 web_vols.push(ca_cert_vol)
246 worker_vols.push(ca_cert_vol)
248 include_recipe 'ssl_cert::ca_certs'
250 import_ca_script = '/usr/local/bin/concourse_import_ca'
251 template "#{bin_dir}/concourse_import_ca" do
252 source 'opt/docker-compose/app/concourse/bin/concourse_import_ca'
258 import_ca_script_vol = "#{bin_dir}/concourse_import_ca:#{import_ca_script}:ro"
259 web_vols.push(import_ca_script_vol)
260 worker_vols.push(import_ca_script_vol)
262 image_entrypoint = node['concourse-ci']['docker-image']['entrypoint']
263 override_config_srvs['concourse-web']['entrypoint'] \
264 = "/bin/sh -c \"#{import_ca_script} && #{image_entrypoint} web\""
265 override_config_srvs['concourse-worker']['entrypoint'] \
266 = "/bin/sh -c \"#{import_ca_script} && #{image_entrypoint} worker\""
267 if config_format_version.to_i == 2
268 node.rm('concourse-ci', 'docker-compose', 'config', 'services', 'concourse-web', 'command')
269 node.rm('concourse-ci', 'docker-compose', 'config', 'services', 'concourse-worker', 'command')
271 node.rm('concourse-ci', 'docker-compose', 'config', 'concourse-web', 'command')
272 node.rm('concourse-ci', 'docker-compose', 'config', 'concourse-worker', 'command')
276 # merge environment hash
277 force_override_config_srvs['concourse-web']['environment'] = web_envs unless web_envs.empty?
278 # reset vlumes array.
279 override_config_srvs['concourse-web']['volumes'] = web_vols unless web_vols.empty?
280 override_config_srvs['concourse-worker']['volumes'] = worker_vols unless worker_vols.empty?
283 source 'opt/docker-compose/app/concourse/.env'
288 # prevent Chef from logging password attribute value.
291 db_passwd: db_passwd,
292 encryption_key: encryption_key,
293 basic_auth_passwd: basic_auth_passwd,
294 oauth_client_id: oauth_client_id,
295 oauth_client_secret: oauth_client_secret
299 template config_file do
300 source 'opt/docker-compose/app/concourse/docker-compose.yml'
306 template "#{bin_dir}/fly_prune_workers_main" do
307 source 'opt/docker-compose/app/concourse/bin/fly_prune_workers_main'
313 basic_auth_username: web_envs_org['CONCOURSE_BASIC_AUTH_USERNAME'],
314 external_url: external_url
318 template "#{bin_dir}/concourse_start" do
319 source 'opt/docker-compose/app/concourse/bin/concourse_start'
327 Note: You must execute the following command manually.
331 $ sudo docker-compose up
333 $ sudo docker-compose down