2 # Cookbook Name:: screwdriver
3 # Recipe:: docker-compose
5 # Copyright 2017, 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 doc_url = 'https://hub.docker.com/r/screwdrivercd/screwdriver/'
22 ::Chef::Recipe.send(:include, SSLCert::Helper)
24 #include_recipe 'platform_utils::kernel_user_namespace'
25 include_recipe 'docker-grid::compose'
32 'socketPath' => '/var/run/docker.sock',
34 'launchVersion' => 'stable',
39 app_dir = node['screwdriver']['docker-compose']['app_dir']
40 bin_dir = node['screwdriver']['docker-compose']['bin_dir']
41 config_dir = node['screwdriver']['docker-compose']['config_dir']
42 data_dir = node['screwdriver']['docker-compose']['data_dir']
43 etc_dir = node['screwdriver']['docker-compose']['etc_dir']
52 resources(directory: dir) rescue directory dir do
60 api_config_file = "#{config_dir}/api-local.yaml"
61 env_file = "#{app_dir}/.env"
62 config_file = "#{app_dir}/docker-compose.yml"
64 api_config_local = nil
65 if File.exist?(api_config_file)
67 api_config_local = YAML.load_file(api_config_file)
71 if File.exist?(env_file)
73 File.open(env_file) do |file|
74 file.each_line do |line|
75 env_local[$1] = $2 if line =~ /^([^=]*)=(.*)$/
81 config_srvs_local = nil
82 if File.exist?(config_file)
84 config_srvs_local = YAML.load_file(config_file)['services']
88 # We use plain Hash objects instead of Chef attribute objects for containg secrets (JWT key pair).
89 override_api_config = node['screwdriver']['api']['config'].to_hash
90 override_store_config = node['screwdriver']['store']['config'].to_hash
91 #override_api_config = node.override['screwdriver']['api']['config'] # NG
92 #override_store_config = node.override['screwdriver']['store']['config'] # NG
94 config_srvs = node['screwdriver']['docker-compose']['config']['services']
95 override_config_srvs = node.override['screwdriver']['docker-compose']['config']['services']
96 force_override_config_srvs = node.force_override['screwdriver']['docker-compose']['config']['services']
99 api_envs_org = config_srvs['api']['environment']
101 api_vols = config_srvs['api']['volumes'].to_a
103 api_port = '9001' # default
104 api_in_port = api_envs_org['PORT']
105 ports = config_srvs['api']['ports']
107 override_config_srvs['api']['ports'] = ["#{api_port}:#{api_in_port}"]
110 elms = port.split(':')
111 api_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == api_in_port
115 override_api_config['executor'] = default_executor if override_api_config['executor'].empty?
118 'jwt_private_key_vault_item',
119 'jwt_public_key_vault_item',
120 'cookie_password_vault_item',
121 'password_vault_item',
123 # for backward compatibility.
124 if node['screwdriver'][vault_item].empty? && !node['screwdriver']['docker-compose'][vault_item].empty?
125 node.force_override['screwdriver'][vault_item] = node['screwdriver']['docker-compose'][vault_item].to_hash
129 jwt_private_key_reset = node['screwdriver']['docker-compose']['jwt_private_key_reset']
130 jwt_private_key = nil
132 jwt_private_key_vault_item = node['screwdriver']['jwt_private_key_vault_item']
133 jwt_public_key_vault_item = node['screwdriver']['jwt_public_key_vault_item']
135 if !jwt_private_key_vault_item.empty?
136 # 1. from Chef Vault (recommended).
137 jwt_private_key = get_vault_item_value(jwt_private_key_vault_item)
138 jwt_public_key = get_vault_item_value(jwt_public_key_vault_item)
139 log 'JWT key pair has been loaded from Chef Vault.'
141 # 2. from Chef attribute (NOT recommended).
142 jwt_private_key = api_envs_org['SECRET_JWT_PRIVATE_KEY']
143 jwt_public_key = api_envs_org['SECRET_JWT_PUBLIC_KEY']
144 if jwt_private_key.nil? || jwt_private_key.empty?
145 if !api_config_local.nil? && !api_config_local['auth']['jwtPrivateKey'].nil? && !jwt_private_key_reset
146 # 3. preserve it from the local config/api-local.yaml file.
147 jwt_private_key = api_config_local['auth']['jwtPrivateKey']
148 jwt_public_key = api_config_local['auth']['jwtPublicKey']
149 log 'JWT key pair is preserved from the local config/api-local.yaml file.'
150 # if !env_local.nil? && !env_local['SECRET_JWT_PRIVATE_KEY'].nil? && !jwt_private_key_reset
151 # # 3. preserve it from the local .env file.
152 # # Note: Docker env file format does not support backslash escaped string yet.
153 # eval "jwt_private_key = %Q(#{env_local['SECRET_JWT_PRIVATE_KEY']})"
154 # eval "jwt_public_key = %Q(#{env_local['SECRET_JWT_PUBLIC_KEY']})"
155 # log 'JWT key pair is preserved from the local .env file.'
159 rsa = OpenSSL::PKey::RSA.generate(2048)
160 jwt_private_key = rsa.export
161 jwt_public_key = rsa.public_key.export
162 log 'JWT key pair has been generated.'
167 override_api_config['auth']['jwtPrivateKey'] = jwt_private_key
168 override_api_config['auth']['jwtPublicKey'] = jwt_public_key
169 # Note: prevent Chef from logging JWT key attribute values. (=> template variables)
170 # However Docker env file format does not support multi-line value and backslash escaped string yet.
171 #api_envs['SECRET_JWT_PRIVATE_KEY'] = '${SECRET_JWT_PRIVATE_KEY}' # Useless
172 #api_envs['SECRET_JWT_PUBLIC_KEY'] = '${SECRET_JWT_PUBLIC_KEY}' # Useless
173 #api_envs['SECRET_JWT_PRIVATE_KEY'] = jwt_private_key # NG
174 #api_envs['SECRET_JWT_PUBLIC_KEY'] = jwt_public_key # NG
176 cookie_password = nil
177 cookie_password_vault_item = node['screwdriver']['cookie_password_vault_item']
178 unless cookie_password_vault_item.empty?
179 cookie_password = get_vault_item_value(cookie_password_vault_item)
180 api_envs['SECRET_COOKIE_PASSWORD'] = '${SECRET_COOKIE_PASSWORD}'
184 password_vault_item = node['screwdriver']['password_vault_item']
185 unless password_vault_item.empty?
186 password = get_vault_item_value(password_vault_item)
187 api_envs['SECRET_PASSWORD'] = '${SECRET_PASSWORD}'
190 node['screwdriver']['api']['scms_vault_items'].each {|scm, props|
191 props.each {|prop, vault_item|
192 unless vault_item.empty?
193 secret = get_vault_item_value(vault_item)
194 override_api_config['scms'][scm]['config'][prop] = secret
200 oauth_client_id = nil
201 oauth_client_id_vault_item = node['screwdriver']['docker-compose']['oauth_client_id_vault_item']
202 unless oauth_client_id_vault_item.empty?
203 oauth_client_id = get_vault_item_value(oauth_client_id_vault_item)
204 api_envs['SECRET_OAUTH_CLIENT_ID'] = '${SECRET_OAUTH_CLIENT_ID}'
207 oauth_client_secret = nil
208 oauth_client_secret_vault_item = node['screwdriver']['docker-compose']['oauth_client_secret_vault_item']
209 unless oauth_client_secret_vault_item.empty?
210 oauth_client_secret = get_vault_item_value(oauth_client_secret_vault_item)
211 api_envs['SECRET_OAUTH_CLIENT_SECRET'] = '${SECRET_OAUTH_CLIENT_SECRET}'
214 webhook_github_secret = nil
215 webhook_github_secret_vault_item = node['screwdriver']['docker-compose']['webhook_github_secret_vault_item']
216 unless webhook_github_secret_vault_item.empty?
217 webhook_github_secret = get_vault_item_value(webhook_github_secret_vault_item)
218 api_envs['WEBHOOK_GITHUB_SECRET'] = '${WEBHOOK_GITHUB_SECRET}'
223 db_username_vault_item = node['screwdriver']['db_username_vault_item']
224 unless db_username_vault_item.empty?
225 db_username = get_vault_item_value(db_username_vault_item)
226 api_envs['DATASTORE_SEQUELIZE_USERNAME'] = '${DB_USERNAME}'
230 db_password_vault_item = node['screwdriver']['db_password_vault_item']
231 unless db_password_vault_item.empty?
232 db_password = get_vault_item_value(db_password_vault_item)
233 api_envs['DATASTORE_SEQUELIZE_PASSWORD'] = '${DB_PASSWORD}'
236 db_root_password = nil
237 db_root_password_vault_item = node['screwdriver']['db_root_password_vault_item']
238 unless db_root_password_vault_item.empty?
239 db_root_password = get_vault_item_value(db_root_password_vault_item)
242 db_dialect = api_envs_org['DATASTORE_SEQUELIZE_DIALECT']
245 api_vols.push("#{data_dir}:/sd-data:rw")
246 api_envs['DATASTORE_SEQUELIZE_STORAGE'] = '/sd-data/storage.db'
247 when 'mysql', 'postgres'
248 override_config_srvs['api']['links'] = ['db']
249 api_envs['DATASTORE_SEQUELIZE_HOST'] = 'db'
253 if db_dialect != 'sqlite'
254 #db_envs_org = config_srvs['db']['environment']
256 db_vols = config_srvs['db']['volumes'].to_a
260 mysql_data_dir = "#{data_dir}/mysql"
261 resources(directory: mysql_data_dir) rescue directory mysql_data_dir do
268 db_envs['MYSQL_DATABASE'] = api_envs_org['DATASTORE_SEQUELIZE_DATABASE']
269 db_envs['MYSQL_USER'] = '${DB_USERNAME}' unless db_username.nil?
270 db_envs['MYSQL_PASSWORD'] = '${DB_PASSWORD}' unless db_password.nil?
271 db_envs['MYSQL_ROOT_PASSWORD'] = '${DB_ROOT_PASSWORD}' unless db_root_password.nil?
272 db_vols.push("#{mysql_data_dir}:/var/lib/mysql:rw")
274 pg_data_dir = "#{data_dir}/postgres"
275 resources(directory: pg_data_dir) rescue directory pg_data_dir do
282 db_envs['POSTGRES_DB'] = api_envs_org['DATASTORE_SEQUELIZE_DATABASE']
283 db_envs['POSTGRES_USER'] = '${DB_USERNAME}' unless db_username.nil?
284 db_envs['POSTGRES_PASSWORD'] = '${DB_PASSWORD}' unless db_password.nil?
285 db_envs['PGDATA'] = '/database'
286 db_vols.push("#{pg_data_dir}:/database:rw")
291 #ui_envs_org = config_srvs['ui']['environment']
293 ui_vols = config_srvs['ui']['volumes'].to_a
295 ui_port = '9000' # default
297 ports = config_srvs['ui']['ports']
299 override_config_srvs['ui']['ports'] = ["#{ui_port}:#{ui_in_port}"]
302 elms = port.split(':')
303 ui_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == ui_in_port
308 store_envs_org = config_srvs['store']['environment']
310 store_vols = config_srvs['store']['volumes'].to_a
312 store_port = '9002' # default
313 store_in_port = store_envs_org['PORT']
314 ports = config_srvs['store']['ports']
316 override_config_srvs['store']['ports'] = ["#{store_port}:#{store_in_port}"]
319 elms = port.split(':')
320 store_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == store_in_port
324 override_store_config['auth']['jwtPublicKey'] = jwt_public_key
325 # Note: prevent Chef from logging JWT key attribute value. (=> template variables)
326 # However Docker env file format does not support multi-line value and backslash escaped string yet.
327 #store_envs['SECRET_JWT_PUBLIC_KEY'] = '${SECRET_JWT_PUBLIC_KEY}' # Useless
328 #store_envs['SECRET_JWT_PUBLIC_KEY'] = jwt_public_key # NG
330 api_uri = api_envs_org['URI']
331 store_uri = store_envs_org['URI']
332 ui_uri = api_uri.gsub(/:\d+/, ":#{ui_port}") # based on the API URI.
334 if node['screwdriver']['with_ssl_cert_cookbook']
335 cn = node['screwdriver']['ssl_cert']['common_name']
336 append_server_ssl_cn(cn)
337 include_recipe 'ssl_cert::server_key_pairs'
339 server_cert = server_cert_content(cn)
340 server_key = server_key_content(cn)
342 api_uri = api_uri.gsub('http://', 'https://')
343 store_uri = store_uri.gsub('http://', 'https://')
345 override_api_config['httpd']['tls'] = {} # for FalseClass by default.
346 override_api_config['httpd']['tls']['cert'] = server_cert
347 override_api_config['httpd']['tls']['key'] = server_key
348 api_envs['IS_HTTPS'] = 'true'
350 override_store_config['httpd']['tls'] = {} # for FalseClass by default.
351 override_store_config['httpd']['tls']['cert'] = server_cert
352 override_store_config['httpd']['tls']['key'] = server_key
354 # Note: Screwdriver UI image does not support TLS settings yet.
355 # https://github.com/screwdriver-cd/screwdriver/issues/377
357 if node['screwdriver']['ui']['tls_setup_mode'] == 'reverseproxy'
358 rproxy_in_port = '9000'
359 ports = config_srvs['reverseproxy']['ports']
361 override_config_srvs['reverseproxy']['ports'] = ["#{ui_port}:#{rproxy_in_port}"]
364 elms = port.split(':')
365 ui_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == rproxy_in_port
368 ui_uri = api_uri.gsub(/:\d+/, ":#{ui_port}") # based on the API URI.
369 # do not expose UI service directly.
370 node.rm('screwdriver', 'docker-compose', 'config', 'services', 'ui', 'ports')
372 rproxy_vols = config_srvs['reverseproxy']['volumes'].to_a
373 rproxy_vols.push("#{etc_dir}/nginx/nginx.conf:/etc/nginx/nginx.conf:ro")
374 # Nginx parent process owner is root.
375 rproxy_vols.push("#{server_cert_path(cn)}:/root/server.crt:ro")
376 rproxy_vols.push("#{server_key_path(cn)}:/root/server.key:ro")
377 # reset vlumes array.
378 override_config_srvs['reverseproxy']['volumes'] = rproxy_vols
380 template "#{etc_dir}/nginx/nginx.conf" do
381 source 'opt/docker-compose/app/screwdriver/etc/nginx/nginx.conf'
388 node.rm('screwdriver', 'docker-compose', 'config', 'services', 'reverseproxy')
389 # TODO: in the future.
392 node.rm('screwdriver', 'docker-compose', 'config', 'services', 'reverseproxy')
396 api_envs['URI'] = api_uri
397 api_envs['ECOSYSTEM_STORE'] = store_uri
398 api_envs['ECOSYSTEM_UI'] = ui_uri
400 ui_envs['ECOSYSTEM_API'] = api_uri
401 ui_envs['ECOSYSTEM_STORE'] = store_uri
403 store_envs['URI'] = store_uri
404 store_envs['ECOSYSTEM_UI'] = ui_uri
407 if node['screwdriver']['docker-compose']['import_ca']
408 node['screwdriver']['ssl_cert']['ca_names'].each {|ca_name|
409 append_ca_name(ca_name)
410 ca_cert_vol = "#{ca_cert_path(ca_name)}:/usr/share/ca-certificates/#{ca_name}.crt:ro"
411 api_vols.push(ca_cert_vol)
412 #ui_vols.push(ca_cert_vol)
414 include_recipe 'ssl_cert::ca_certs'
416 import_ca_script = '/usr/local/bin/screwdriver_import_ca'
417 template "#{bin_dir}/screwdriver_import_ca" do
418 source 'opt/docker-compose/app/screwdriver/bin/screwdriver_import_ca'
424 import_ca_script_vol = "#{bin_dir}/screwdriver_import_ca:#{import_ca_script}:ro"
425 api_vols.push(import_ca_script_vol)
426 #ui_vols.push(import_ca_script_vol)
428 api_command = config_srvs['api']['command']
429 override_config_srvs['api']['command'] \
430 = "/bin/sh -c \"#{import_ca_script} && #{api_command}\""
437 local_yaml_file = "#{config_dir}/#{srv}-local.yaml"
442 srv_config = override_api_config
444 srv_vols = store_vols
445 srv_config = override_store_config
448 template local_yaml_file do
449 source "opt/docker-compose/app/screwdriver/config/#{srv}-local.yaml"
454 # prevent Chef from logging password attribute value.
460 srv_vols.push("#{local_yaml_file}:/config/local.yaml:ro")
463 # merge environment hash
464 force_override_config_srvs['api']['environment'] = api_envs unless api_envs.empty?
465 force_override_config_srvs['ui']['environment'] = ui_envs unless ui_envs.empty?
466 force_override_config_srvs['store']['environment'] = store_envs unless store_envs.empty?
467 if db_dialect != 'sqlite'
468 force_override_config_srvs['db']['environment'] = db_envs unless db_envs.empty?
470 # reset vlumes array.
471 override_config_srvs['api']['volumes'] = api_vols unless api_vols.empty?
472 override_config_srvs['ui']['volumes'] = ui_vols unless ui_vols.empty?
473 override_config_srvs['store']['volumes'] = store_vols unless store_vols.empty?
474 if db_dialect != 'sqlite'
475 override_config_srvs['db']['volumes'] = db_vols unless db_vols.empty?
479 source 'opt/docker-compose/app/screwdriver/.env'
484 # prevent Chef from logging password attribute value.
487 cookie_password: cookie_password,
489 db_username: db_username,
490 db_password: db_password,
491 db_root_password: db_root_password,
493 # JWT keys setting -> /config/local.yaml
494 #jwt_private_key: jwt_private_key,
495 #jwt_public_key: jwt_public_key,
496 # SCM secrets setting -> /config/local.yaml
497 #oauth_client_id: oauth_client_id,
498 #oauth_client_secret: oauth_client_secret,
499 #webhook_github_secret: webhook_github_secret
503 template config_file do
504 source 'opt/docker-compose/app/screwdriver/docker-compose.yml'
510 log 'screwdriver docker-compose post install message' do
512 Note: You must execute the following command manually.
516 $ sudo docker-compose up -d
518 $ sudo docker-compose down