OSDN Git Service

22df17a93c764839d39002e312196105abfdfdd4
[metasearch/grid-chef-repo.git] / cookbooks / screwdriver / recipes / docker-compose.rb
1 #
2 # Cookbook Name:: screwdriver
3 # Recipe:: docker-compose
4 #
5 # Copyright 2017, whitestar
6 #
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
10 #
11 #     http://www.apache.org/licenses/LICENSE-2.0
12 #
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.
18 #
19
20 doc_url = 'https://hub.docker.com/r/screwdrivercd/screwdriver/'
21
22 ::Chef::Recipe.send(:include, SSLCert::Helper)
23
24 #include_recipe 'platform_utils::kernel_user_namespace'
25 include_recipe 'docker-grid::compose'
26
27 default_executor = {
28   'plugin' => 'docker',
29   'docker' => {
30     'options' => {
31       'docker' => {
32         'socketPath' => '/var/run/docker.sock',
33       },
34       'launchVersion' => 'stable',
35     },
36   },
37 }
38
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']
44
45 [
46   app_dir,
47   bin_dir,
48   config_dir,
49   data_dir,
50   "#{etc_dir}/nginx",
51 ].each {|dir|
52   resources(directory: dir) rescue directory dir do
53     owner 'root'
54     group 'root'
55     mode '0755'
56     recursive true
57   end
58 }
59
60 api_config_file = "#{config_dir}/api-local.yaml"
61 env_file = "#{app_dir}/.env"
62 config_file = "#{app_dir}/docker-compose.yml"
63
64 api_config_local = nil
65 if File.exist?(api_config_file)
66   require 'yaml'
67   api_config_local = YAML.load_file(api_config_file)
68 end
69
70 env_local = nil
71 if File.exist?(env_file)
72   env_local = {}
73   File.open(env_file) do |file|
74     file.each_line do |line|
75       env_local[$1] = $2 if line =~ /^([^=]*)=(.*)$/
76     end
77   end
78 end
79
80 =begin
81 config_srvs_local = nil
82 if File.exist?(config_file)
83   require 'yaml'
84   config_srvs_local = YAML.load_file(config_file)['services']
85 end
86 =end
87
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
93
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']
97
98 # api
99 api_envs_org = config_srvs['api']['environment']
100 api_envs = {}
101 api_vols = config_srvs['api']['volumes'].to_a
102
103 api_port = '9001'  # default
104 api_in_port = api_envs_org['PORT']
105 ports = config_srvs['api']['ports']
106 if ports.empty?
107   override_config_srvs['api']['ports'] = ["#{api_port}:#{api_in_port}"]
108 else
109   ports.each {|port|
110     elms = port.split(':')
111     api_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == api_in_port
112   }
113 end
114 api_vols.push("#{data_dir}:/sd-data:rw")
115
116 override_api_config['executor'] = default_executor if override_api_config['executor'].empty?
117
118 [
119   'jwt_private_key_vault_item',
120   'jwt_public_key_vault_item',
121   'cookie_password_vault_item',
122   'password_vault_item',
123 ].each {|vault_item|
124   # for backward compatibility.
125   if node['screwdriver'][vault_item].empty? && !node['screwdriver']['docker-compose'][vault_item].empty?
126     node.force_override['screwdriver'][vault_item] = node['screwdriver']['docker-compose'][vault_item].to_hash
127   end
128 }
129
130 jwt_private_key_reset = node['screwdriver']['docker-compose']['jwt_private_key_reset']
131 jwt_private_key = nil
132 jwt_public_key  = nil
133 jwt_private_key_vault_item = node['screwdriver']['jwt_private_key_vault_item']
134 jwt_public_key_vault_item  = node['screwdriver']['jwt_public_key_vault_item']
135
136 if !jwt_private_key_vault_item.empty?
137   # 1. from Chef Vault (recommended).
138   jwt_private_key = get_vault_item_value(jwt_private_key_vault_item)
139   jwt_public_key  = get_vault_item_value(jwt_public_key_vault_item)
140   log 'JWT key pair has been loaded from Chef Vault.'
141 else
142   # 2. from Chef attribute (NOT recommended).
143   jwt_private_key = api_envs_org['SECRET_JWT_PRIVATE_KEY']
144   jwt_public_key  = api_envs_org['SECRET_JWT_PUBLIC_KEY']
145   if jwt_private_key.nil? || jwt_private_key.empty?
146     if !api_config_local.nil? && !api_config_local['auth']['jwtPrivateKey'].nil? && !jwt_private_key_reset
147       # 3. preserve it from the local config/api-local.yaml file.
148       jwt_private_key = api_config_local['auth']['jwtPrivateKey']
149       jwt_public_key  = api_config_local['auth']['jwtPublicKey']
150       log 'JWT key pair is preserved from the local config/api-local.yaml file.'
151     # if !env_local.nil? && !env_local['SECRET_JWT_PRIVATE_KEY'].nil? && !jwt_private_key_reset
152     #   # 3. preserve it from the local .env file.
153     #   # Note: Docker env file format does not support backslash escaped string yet.
154     #   eval "jwt_private_key = %Q(#{env_local['SECRET_JWT_PRIVATE_KEY']})"
155     #   eval "jwt_public_key  = %Q(#{env_local['SECRET_JWT_PUBLIC_KEY']})"
156     #   log 'JWT key pair is preserved from the local .env file.'
157     else
158       # 4. auto generate.
159       require 'openssl'
160       rsa = OpenSSL::PKey::RSA.generate(2048)
161       jwt_private_key = rsa.export
162       jwt_public_key  = rsa.public_key.export
163       log 'JWT key pair has been generated.'
164     end
165   end
166 end
167
168 override_api_config['auth']['jwtPrivateKey'] = jwt_private_key
169 override_api_config['auth']['jwtPublicKey'] = jwt_public_key
170 # Note: prevent Chef from logging JWT key attribute values. (=> template variables)
171 # However Docker env file format does not support multi-line value and backslash escaped string yet.
172 #api_envs['SECRET_JWT_PRIVATE_KEY'] = '${SECRET_JWT_PRIVATE_KEY}'  # Useless
173 #api_envs['SECRET_JWT_PUBLIC_KEY']  = '${SECRET_JWT_PUBLIC_KEY}'   # Useless
174 #api_envs['SECRET_JWT_PRIVATE_KEY'] = jwt_private_key  # NG
175 #api_envs['SECRET_JWT_PUBLIC_KEY']  = jwt_public_key   # NG
176
177 cookie_password = nil
178 cookie_password_vault_item = node['screwdriver']['cookie_password_vault_item']
179 unless cookie_password_vault_item.empty?
180   cookie_password = get_vault_item_value(cookie_password_vault_item)
181   api_envs['SECRET_COOKIE_PASSWORD'] = '${SECRET_COOKIE_PASSWORD}'
182 end
183
184 password = nil
185 password_vault_item = node['screwdriver']['password_vault_item']
186 unless password_vault_item.empty?
187   password = get_vault_item_value(password_vault_item)
188   api_envs['SECRET_PASSWORD'] = '${SECRET_PASSWORD}'
189 end
190
191 node['screwdriver']['api']['scms_vault_items'].each {|scm, props|
192   props.each {|prop, vault_item|
193     unless vault_item.empty?
194       secret = get_vault_item_value(vault_item)
195       override_api_config['scms'][scm]['config'][prop] = secret
196     end
197   }
198 }
199 =begin
200 # DEPRECATED!!
201 oauth_client_id = nil
202 oauth_client_id_vault_item = node['screwdriver']['docker-compose']['oauth_client_id_vault_item']
203 unless oauth_client_id_vault_item.empty?
204   oauth_client_id = get_vault_item_value(oauth_client_id_vault_item)
205   api_envs['SECRET_OAUTH_CLIENT_ID'] = '${SECRET_OAUTH_CLIENT_ID}'
206 end
207
208 oauth_client_secret = nil
209 oauth_client_secret_vault_item = node['screwdriver']['docker-compose']['oauth_client_secret_vault_item']
210 unless oauth_client_secret_vault_item.empty?
211   oauth_client_secret = get_vault_item_value(oauth_client_secret_vault_item)
212   api_envs['SECRET_OAUTH_CLIENT_SECRET'] = '${SECRET_OAUTH_CLIENT_SECRET}'
213 end
214
215 webhook_github_secret = nil
216 webhook_github_secret_vault_item = node['screwdriver']['docker-compose']['webhook_github_secret_vault_item']
217 unless webhook_github_secret_vault_item.empty?
218   webhook_github_secret = get_vault_item_value(webhook_github_secret_vault_item)
219   api_envs['WEBHOOK_GITHUB_SECRET'] = '${WEBHOOK_GITHUB_SECRET}'
220 end
221 =end
222
223 # ui
224 #ui_envs_org = config_srvs['ui']['environment']
225 ui_envs = {}
226 ui_vols = config_srvs['ui']['volumes'].to_a
227
228 ui_port = '9000'  # default
229 ui_in_port = '80'
230 ports = config_srvs['ui']['ports']
231 if ports.empty?
232   override_config_srvs['ui']['ports'] = ["#{ui_port}:#{ui_in_port}"]
233 else
234   ports.each {|port|
235     elms = port.split(':')
236     ui_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == ui_in_port
237   }
238 end
239
240 # store
241 store_envs_org = config_srvs['store']['environment']
242 store_envs = {}
243 store_vols = config_srvs['store']['volumes'].to_a
244
245 store_port = '9002'  # default
246 store_in_port = store_envs_org['PORT']
247 ports = config_srvs['store']['ports']
248 if ports.empty?
249   override_config_srvs['store']['ports'] = ["#{store_port}:#{store_in_port}"]
250 else
251   ports.each {|port|
252     elms = port.split(':')
253     store_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == store_in_port
254   }
255 end
256
257 override_store_config['auth']['jwtPublicKey'] = jwt_public_key
258 # Note: prevent Chef from logging JWT key attribute value. (=> template variables)
259 # However Docker env file format does not support multi-line value and backslash escaped string yet.
260 #store_envs['SECRET_JWT_PUBLIC_KEY'] = '${SECRET_JWT_PUBLIC_KEY}'  # Useless
261 #store_envs['SECRET_JWT_PUBLIC_KEY']  = jwt_public_key  # NG
262
263 api_uri = api_envs_org['URI']
264 store_uri = store_envs_org['URI']
265 ui_uri = api_uri.gsub(/:\d+/, ":#{ui_port}")  # based on the API URI.
266
267 if node['screwdriver']['with_ssl_cert_cookbook']
268   cn = node['screwdriver']['ssl_cert']['common_name']
269   append_server_ssl_cn(cn)
270   include_recipe 'ssl_cert::server_key_pairs'
271
272   server_cert = server_cert_content(cn)
273   server_key = server_key_content(cn)
274
275   api_uri = api_uri.gsub('http://', 'https://')
276   store_uri = store_uri.gsub('http://', 'https://')
277
278   override_api_config['httpd']['tls'] = {}  # for FalseClass by default.
279   override_api_config['httpd']['tls']['cert'] = server_cert
280   override_api_config['httpd']['tls']['key'] = server_key
281   api_envs['IS_HTTPS'] = 'true'
282
283   override_store_config['httpd']['tls'] = {}  # for FalseClass by default.
284   override_store_config['httpd']['tls']['cert'] = server_cert
285   override_store_config['httpd']['tls']['key'] = server_key
286
287   # Note: Screwdriver UI image does not support TLS settings yet.
288   # https://github.com/screwdriver-cd/screwdriver/issues/377
289
290   if node['screwdriver']['ui']['tls_setup_mode'] == 'reverseproxy'
291     rproxy_in_port = '9000'
292     ports = config_srvs['reverseproxy']['ports']
293     if ports.empty?
294       override_config_srvs['reverseproxy']['ports'] = ["#{ui_port}:#{rproxy_in_port}"]
295     else
296       ports.each {|port|
297         elms = port.split(':')
298         ui_port = (elms.size == 2 ? elms[0] : elms[1]) if elms.last == rproxy_in_port
299       }
300     end
301     ui_uri = api_uri.gsub(/:\d+/, ":#{ui_port}")  # based on the API URI.
302     # do not expose UI service directly.
303     node.rm('screwdriver', 'docker-compose', 'config', 'services', 'ui', 'ports')
304
305     rproxy_vols = config_srvs['reverseproxy']['volumes'].to_a
306     rproxy_vols.push("#{etc_dir}/nginx/nginx.conf:/etc/nginx/nginx.conf:ro")
307     # Nginx parent process owner is root.
308     rproxy_vols.push("#{server_cert_path(cn)}:/root/server.crt:ro")
309     rproxy_vols.push("#{server_key_path(cn)}:/root/server.key:ro")
310     # reset vlumes array.
311     override_config_srvs['reverseproxy']['volumes'] = rproxy_vols
312
313     template "#{etc_dir}/nginx/nginx.conf" do
314       source 'opt/docker-compose/app/screwdriver/etc/nginx/nginx.conf'
315       owner 'root'
316       group 'root'
317       mode '0644'
318       action :create
319     end
320   else
321     node.rm('screwdriver', 'docker-compose', 'config', 'services', 'reverseproxy')
322     # TODO: in the future.
323   end
324 else
325   node.rm('screwdriver', 'docker-compose', 'config', 'services', 'reverseproxy')
326 end
327
328 # normalize URIs
329 api_envs['URI'] = api_uri
330 api_envs['ECOSYSTEM_STORE'] = store_uri
331 api_envs['ECOSYSTEM_UI'] = ui_uri
332
333 ui_envs['ECOSYSTEM_API'] = api_uri
334 ui_envs['ECOSYSTEM_STORE'] = store_uri
335
336 store_envs['URI'] = store_uri
337 store_envs['ECOSYSTEM_UI'] = ui_uri
338
339 # Common
340 if node['screwdriver']['docker-compose']['import_ca']
341   node['screwdriver']['ssl_cert']['ca_names'].each {|ca_name|
342     append_ca_name(ca_name)
343     ca_cert_vol = "#{ca_cert_path(ca_name)}:/usr/share/ca-certificates/#{ca_name}.crt:ro"
344     api_vols.push(ca_cert_vol)
345     #ui_vols.push(ca_cert_vol)
346   }
347   include_recipe 'ssl_cert::ca_certs'
348
349   import_ca_script = '/usr/local/bin/screwdriver_import_ca'
350   template "#{bin_dir}/screwdriver_import_ca" do
351     source 'opt/docker-compose/app/screwdriver/bin/screwdriver_import_ca'
352     owner 'root'
353     group 'root'
354     mode '0755'
355     action :create
356   end
357   import_ca_script_vol = "#{bin_dir}/screwdriver_import_ca:#{import_ca_script}:ro"
358   api_vols.push(import_ca_script_vol)
359   #ui_vols.push(import_ca_script_vol)
360
361   api_command = config_srvs['api']['command']
362   override_config_srvs['api']['command'] \
363     = "/bin/sh -c \"#{import_ca_script} && #{api_command}\""
364 end
365
366 [
367   'api',
368   'store',
369 ].each {|srv|
370   local_yaml_file = "#{config_dir}/#{srv}-local.yaml"
371   srv_vols = nil
372   srv_config = nil
373   if srv == 'api'
374     srv_vols = api_vols
375     srv_config = override_api_config
376   elsif srv == 'store'
377     srv_vols = store_vols
378     srv_config = override_store_config
379   end
380
381   template local_yaml_file do
382     source "opt/docker-compose/app/screwdriver/config/#{srv}-local.yaml"
383     owner 'root'
384     group 'root'
385     mode '0600'
386     sensitive true
387     # prevent Chef from logging password attribute value.
388     variables(
389       config: srv_config
390     )
391   end
392
393   srv_vols.push("#{local_yaml_file}:/config/local.yaml:ro")
394 }
395
396 # merge environment hash
397 force_override_config_srvs['api']['environment'] = api_envs unless api_envs.empty?
398 force_override_config_srvs['ui']['environment'] = ui_envs unless ui_envs.empty?
399 force_override_config_srvs['store']['environment'] = store_envs unless store_envs.empty?
400 # reset vlumes array.
401 override_config_srvs['api']['volumes'] = api_vols unless api_vols.empty?
402 override_config_srvs['ui']['volumes'] = ui_vols unless ui_vols.empty?
403 override_config_srvs['store']['volumes'] = store_vols unless store_vols.empty?
404
405 template env_file do
406   source 'opt/docker-compose/app/screwdriver/.env'
407   owner 'root'
408   group 'root'
409   mode '0600'
410   sensitive true
411   # prevent Chef from logging password attribute value.
412   variables(
413     # secrets
414     # JWT keys setting -> /config/local.yaml
415     #jwt_private_key: jwt_private_key,
416     #jwt_public_key: jwt_public_key,
417     cookie_password: cookie_password,
418     password: password,
419     # SCM secrets setting -> /config/local.yaml
420     #oauth_client_id: oauth_client_id,
421     #oauth_client_secret: oauth_client_secret,
422     #webhook_github_secret: webhook_github_secret
423   )
424 end
425
426 template config_file do
427   source 'opt/docker-compose/app/screwdriver/docker-compose.yml'
428   owner 'root'
429   group 'root'
430   mode '0644'
431 end
432
433 log 'screwdriver docker-compose post install message' do
434   message <<-"EOM"
435 Note: You must execute the following command manually.
436     See #{doc_url}
437     * Start:
438       $ cd #{app_dir}
439       $ sudo docker-compose up -d
440     * Stop
441       $ sudo docker-compose down
442 EOM
443 end