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'
27 app_dir = node['screwdriver']['docker-compose']['app_dir']
28 bin_dir = node['screwdriver']['docker-compose']['bin_dir']
29 config_dir = node['screwdriver']['docker-compose']['config_dir']
30 data_dir = node['screwdriver']['docker-compose']['data_dir']
31 etc_dir = node['screwdriver']['docker-compose']['etc_dir']
40 resources(directory: dir) rescue directory dir do
48 api_config_file = "#{config_dir}/api-local.yaml"
49 env_file = "#{app_dir}/.env"
50 config_file = "#{app_dir}/docker-compose.yml"
52 api_config_local = nil
53 if File.exist?(api_config_file)
55 api_config_local = YAML.load_file(api_config_file)
59 if File.exist?(env_file)
61 File.open(env_file) do |file|
62 file.each_line do |line|
63 env_local[$1] = $2 if line =~ /^([^=]*)=(.*)$/
69 config_srvs_local = nil
70 if File.exist?(config_file)
72 config_srvs_local = YAML.load_file(config_file)['services']
76 # We use plain Hash objects instead of Chef attribute objects for containg secrets (JWT key pair).
77 override_api_config = node['screwdriver']['api']['config'].to_hash
78 override_store_config = node['screwdriver']['store']['config'].to_hash
79 #override_api_config = node.override['screwdriver']['api']['config'] # NG
80 #override_store_config = node.override['screwdriver']['store']['config'] # NG
82 config_srvs = node['screwdriver']['docker-compose']['config']['services']
83 override_config_srvs = node.override['screwdriver']['docker-compose']['config']['services']
84 force_override_config_srvs = node.force_override['screwdriver']['docker-compose']['config']['services']
87 api_envs_org = config_srvs['api']['environment']
89 api_vols = config_srvs['api']['volumes'].to_a
91 api_port = '9001' # default
92 api_in_port = api_envs_org['PORT']
93 ports = config_srvs['api']['ports']
95 override_config_srvs['api']['ports'] = ["#{api_port}:#{api_in_port}"]
98 elms = port.split(':')
99 api_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == api_in_port
102 api_vols.push("#{data_dir}:/sd-data:rw")
104 jwt_private_key_reset = node['screwdriver']['docker-compose']['jwt_private_key_reset']
105 jwt_private_key = nil
107 jwt_private_key_vault_item = node['screwdriver']['docker-compose']['jwt_private_key_vault_item']
108 jwt_public_key_vault_item = node['screwdriver']['docker-compose']['jwt_public_key_vault_item']
110 if !jwt_private_key_vault_item.empty?
111 # 1. from Chef Vault (recommended).
112 jwt_private_key = get_vault_item_value(jwt_private_key_vault_item)
113 jwt_public_key = get_vault_item_value(jwt_public_key_vault_item)
114 log 'JWT key pair has been loaded from Chef Vault.'
116 # 2. from Chef attribute (NOT recommended).
117 jwt_private_key = api_envs_org['SECRET_JWT_PRIVATE_KEY']
118 jwt_public_key = api_envs_org['SECRET_JWT_PUBLIC_KEY']
119 if jwt_private_key.nil? || jwt_private_key.empty?
120 if !api_config_local.nil? && !api_config_local['auth']['jwtPrivateKey'].nil? && !jwt_private_key_reset
121 # 3. preserve it from the local config/api-local.yaml file.
122 jwt_private_key = api_config_local['auth']['jwtPrivateKey']
123 jwt_public_key = api_config_local['auth']['jwtPublicKey']
124 log 'JWT key pair is preserved from the local config/api-local.yaml file.'
125 # if !env_local.nil? && !env_local['SECRET_JWT_PRIVATE_KEY'].nil? && !jwt_private_key_reset
126 # # 3. preserve it from the local .env file.
127 # # Note: Docker env file format does not support backslash escaped string yet.
128 # eval "jwt_private_key = %Q(#{env_local['SECRET_JWT_PRIVATE_KEY']})"
129 # eval "jwt_public_key = %Q(#{env_local['SECRET_JWT_PUBLIC_KEY']})"
130 # log 'JWT key pair is preserved from the local .env file.'
134 rsa = OpenSSL::PKey::RSA.generate(2048)
135 jwt_private_key = rsa.export
136 jwt_public_key = rsa.public_key.export
137 log 'JWT key pair has been generated.'
142 override_api_config['auth']['jwtPrivateKey'] = jwt_private_key
143 override_api_config['auth']['jwtPublicKey'] = jwt_public_key
144 # Note: prevent Chef from logging JWT key attribute values. (=> template variables)
145 # However Docker env file format does not support multi-line value and backslash escaped string yet.
146 #api_envs['SECRET_JWT_PRIVATE_KEY'] = '${SECRET_JWT_PRIVATE_KEY}' # Useless
147 #api_envs['SECRET_JWT_PUBLIC_KEY'] = '${SECRET_JWT_PUBLIC_KEY}' # Useless
148 #api_envs['SECRET_JWT_PRIVATE_KEY'] = jwt_private_key # NG
149 #api_envs['SECRET_JWT_PUBLIC_KEY'] = jwt_public_key # NG
151 cookie_password = nil
152 cookie_password_vault_item = node['screwdriver']['docker-compose']['cookie_password_vault_item']
153 unless cookie_password_vault_item.empty?
154 cookie_password = get_vault_item_value(cookie_password_vault_item)
155 api_envs['SECRET_COOKIE_PASSWORD'] = '${SECRET_COOKIE_PASSWORD}'
159 password_vault_item = node['screwdriver']['docker-compose']['password_vault_item']
160 unless password_vault_item.empty?
161 password = get_vault_item_value(password_vault_item)
162 api_envs['SECRET_PASSWORD'] = '${SECRET_PASSWORD}'
165 oauth_client_id = nil
166 oauth_client_id_vault_item = node['screwdriver']['docker-compose']['oauth_client_id_vault_item']
167 unless oauth_client_id_vault_item.empty?
168 oauth_client_id = get_vault_item_value(oauth_client_id_vault_item)
169 api_envs['SECRET_OAUTH_CLIENT_ID'] = '${SECRET_OAUTH_CLIENT_ID}'
172 oauth_client_secret = nil
173 oauth_client_secret_vault_item = node['screwdriver']['docker-compose']['oauth_client_secret_vault_item']
174 unless oauth_client_secret_vault_item.empty?
175 oauth_client_secret = get_vault_item_value(oauth_client_secret_vault_item)
176 api_envs['SECRET_OAUTH_CLIENT_SECRET'] = '${SECRET_OAUTH_CLIENT_SECRET}'
179 webhook_github_secret = nil
180 webhook_github_secret_vault_item = node['screwdriver']['docker-compose']['webhook_github_secret_vault_item']
181 unless webhook_github_secret_vault_item.empty?
182 webhook_github_secret = get_vault_item_value(webhook_github_secret_vault_item)
183 api_envs['WEBHOOK_GITHUB_SECRET'] = '${WEBHOOK_GITHUB_SECRET}'
187 #ui_envs_org = config_srvs['ui']['environment']
189 ui_vols = config_srvs['ui']['volumes'].to_a
191 ui_port = '9000' # default
193 ports = config_srvs['ui']['ports']
195 override_config_srvs['ui']['ports'] = ["#{ui_port}:#{ui_in_port}"]
198 elms = port.split(':')
199 ui_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == ui_in_port
204 store_envs_org = config_srvs['store']['environment']
206 store_vols = config_srvs['store']['volumes'].to_a
208 store_port = '9002' # default
209 store_in_port = store_envs_org['PORT']
210 ports = config_srvs['store']['ports']
212 override_config_srvs['store']['ports'] = ["#{store_port}:#{store_in_port}"]
215 elms = port.split(':')
216 store_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == store_in_port
220 override_store_config['auth']['jwtPublicKey'] = jwt_public_key
221 # Note: prevent Chef from logging JWT key attribute value. (=> template variables)
222 # However Docker env file format does not support multi-line value and backslash escaped string yet.
223 #store_envs['SECRET_JWT_PUBLIC_KEY'] = '${SECRET_JWT_PUBLIC_KEY}' # Useless
224 #store_envs['SECRET_JWT_PUBLIC_KEY'] = jwt_public_key # NG
226 api_uri = api_envs_org['URI']
227 store_uri = store_envs_org['URI']
228 ui_uri = api_uri.gsub(/:\d+/, ":#{ui_port}") # based on the API URI.
230 if node['screwdriver']['with_ssl_cert_cookbook']
231 cn = node['screwdriver']['ssl_cert']['common_name']
232 append_server_ssl_cn(cn)
233 include_recipe 'ssl_cert::server_key_pairs'
235 server_cert = server_cert_content(cn)
236 server_key = server_key_content(cn)
238 api_uri = api_uri.gsub('http://', 'https://')
239 store_uri = store_uri.gsub('http://', 'https://')
241 override_api_config['httpd']['tls'] = {} # for FalseClass by default.
242 override_api_config['httpd']['tls']['cert'] = server_cert
243 override_api_config['httpd']['tls']['key'] = server_key
244 api_envs['IS_HTTPS'] = 'true'
246 override_store_config['httpd']['tls'] = {} # for FalseClass by default.
247 override_store_config['httpd']['tls']['cert'] = server_cert
248 override_store_config['httpd']['tls']['key'] = server_key
250 # Note: Screwdriver UI image does not support TLS settings yet.
251 # https://github.com/screwdriver-cd/screwdriver/issues/377
253 if node['screwdriver']['ui']['tls_setup_mode'] == 'reverseproxy'
254 rproxy_in_port = '9000'
255 ports = config_srvs['reverseproxy']['ports']
257 override_config_srvs['reverseproxy']['ports'] = ["#{ui_port}:#{rproxy_in_port}"]
260 elms = port.split(':')
261 ui_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == rproxy_in_port
264 ui_uri = api_uri.gsub(/:\d+/, ":#{ui_port}") # based on the API URI.
265 # do not expose UI service directly.
266 node.rm('screwdriver', 'docker-compose', 'config', 'services', 'ui', 'ports')
268 rproxy_vols = config_srvs['reverseproxy']['volumes'].to_a
269 rproxy_vols.push("#{etc_dir}/nginx/nginx.conf:/etc/nginx/nginx.conf:ro")
270 # Nginx parent process owner is root.
271 rproxy_vols.push("#{server_cert_path(cn)}:/root/server.crt:ro")
272 rproxy_vols.push("#{server_key_path(cn)}:/root/server.key:ro")
273 # reset vlumes array.
274 override_config_srvs['reverseproxy']['volumes'] = rproxy_vols
276 template "#{etc_dir}/nginx/nginx.conf" do
277 source 'opt/docker-compose/app/screwdriver/etc/nginx/nginx.conf'
284 node.rm('screwdriver', 'docker-compose', 'config', 'services', 'reverseproxy')
285 # TODO: in the future.
288 node.rm('screwdriver', 'docker-compose', 'config', 'services', 'reverseproxy')
292 api_envs['URI'] = api_uri
293 api_envs['ECOSYSTEM_STORE'] = store_uri
294 api_envs['ECOSYSTEM_UI'] = ui_uri
296 ui_envs['ECOSYSTEM_API'] = api_uri
297 ui_envs['ECOSYSTEM_STORE'] = store_uri
299 store_envs['URI'] = store_uri
300 store_envs['ECOSYSTEM_UI'] = ui_uri
303 if node['screwdriver']['docker-compose']['import_ca']
304 node['screwdriver']['ssl_cert']['ca_names'].each {|ca_name|
305 append_ca_name(ca_name)
306 ca_cert_vol = "#{ca_cert_path(ca_name)}:/usr/share/ca-certificates/#{ca_name}.crt:ro"
307 api_vols.push(ca_cert_vol)
308 #ui_vols.push(ca_cert_vol)
310 include_recipe 'ssl_cert::ca_certs'
312 import_ca_script = '/usr/local/bin/screwdriver_import_ca'
313 template "#{bin_dir}/screwdriver_import_ca" do
314 source 'opt/docker-compose/app/screwdriver/bin/screwdriver_import_ca'
320 import_ca_script_vol = "#{bin_dir}/screwdriver_import_ca:#{import_ca_script}:ro"
321 api_vols.push(import_ca_script_vol)
322 #ui_vols.push(import_ca_script_vol)
324 api_command = config_srvs['api']['command']
325 override_config_srvs['api']['command'] \
326 = "/bin/sh -c \"#{import_ca_script} && #{api_command}\""
333 local_yaml_file = "#{config_dir}/#{srv}-local.yaml"
338 srv_config = override_api_config
340 srv_vols = store_vols
341 srv_config = override_store_config
344 template local_yaml_file do
345 source "opt/docker-compose/app/screwdriver/config/#{srv}-local.yaml"
350 # prevent Chef from logging password attribute value.
356 srv_vols.push("#{local_yaml_file}:/config/local.yaml:ro")
359 # merge environment hash
360 force_override_config_srvs['api']['environment'] = api_envs unless api_envs.empty?
361 force_override_config_srvs['ui']['environment'] = ui_envs unless ui_envs.empty?
362 force_override_config_srvs['store']['environment'] = store_envs unless store_envs.empty?
363 # reset vlumes array.
364 override_config_srvs['api']['volumes'] = api_vols unless api_vols.empty?
365 override_config_srvs['ui']['volumes'] = ui_vols unless ui_vols.empty?
366 override_config_srvs['store']['volumes'] = store_vols unless store_vols.empty?
369 source 'opt/docker-compose/app/screwdriver/.env'
374 # prevent Chef from logging password attribute value.
377 # JWT keys setting -> /config/local.yaml
378 #jwt_private_key: jwt_private_key,
379 #jwt_public_key: jwt_public_key,
380 cookie_password: cookie_password,
382 oauth_client_id: oauth_client_id,
383 oauth_client_secret: oauth_client_secret,
384 webhook_github_secret: webhook_github_secret
388 template config_file do
389 source 'opt/docker-compose/app/screwdriver/docker-compose.yml'
396 Note: You must execute the following command manually.
400 $ sudo docker-compose up -d
402 $ sudo docker-compose down