#
# Cookbook Name:: concourse-ci
# Recipe:: docker-compose
#
# Copyright 2017, whitestar
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'securerandom'

doc_url = 'https://concourse.ci/docker-repository.html'

include_recipe 'platform_utils::kernel_user_namespace'
include_recipe 'docker-grid::compose'

app_dir = node['concourse-ci']['docker-compose']['app_dir']
bin_dir = "#{app_dir}/bin"
pgdata_dir = node['concourse-ci']['docker-compose']['pgdata_dir']
web_keys_dir = node['concourse-ci']['docker-compose']['web_keys_dir']
worker_keys_dir = node['concourse-ci']['docker-compose']['worker_keys_dir']

[
  app_dir,
  bin_dir,
  web_keys_dir,
  worker_keys_dir,
].each {|dir|
  resources(directory: dir) rescue directory dir do
    owner 'root'
    group 'root'
    mode '0755'
    recursive true
  end
}

# DB persistent
resources(directory: pgdata_dir) rescue directory pgdata_dir do
  #owner 999
  group 'root'
  mode '0700'
  recursive true
end if !pgdata_dir.nil? && !pgdata_dir.empty?

bash 'ssh-keygen_keys' do
  code <<-"EOH"
    rm -f #{web_keys_dir}/*
    ssh-keygen -t rsa -f #{web_keys_dir}/tsa_host_key -N ''
    ssh-keygen -t rsa -f #{web_keys_dir}/session_signing_key -N ''
    rm -f #{worker_keys_dir}/*
    ssh-keygen -t rsa -f #{worker_keys_dir}/worker_key -N ''
    cp #{worker_keys_dir}/worker_key.pub #{web_keys_dir}/authorized_worker_keys
    cp #{web_keys_dir}/tsa_host_key.pub #{worker_keys_dir}
  EOH
  action :run
  unless node['concourse-ci']['docker-compose']['ssh_keys_reset']
    not_if { File.exist?("#{web_keys_dir}/tsa_host_key") }
    not_if { File.exist?("#{web_keys_dir}/session_signing_key") }
    not_if { File.exist?("#{worker_keys_dir}/worker_key") }
  end
end

env_file = "#{app_dir}/.env"
config_file = "#{app_dir}/docker-compose.yml"

env_local = nil
if File.exist?(env_file)
  env_local = {}
  File.open(env_file) do |file|
    file.each_line do |line|
      env_local[$1] = $2 if line =~ /^(.*)=(.*)$/
    end
  end
end

config_srvs_local = nil
if File.exist?(config_file)
  require 'yaml'
  config_srvs_local = YAML.load_file(config_file)
  config_srvs_local = config_srvs_local['services'] if config_srvs_local.key?('version') && config_srvs_local['version'] == '2'
end

config_format_version = node['concourse-ci']['docker-compose']['config_format_version']

# if config_format_version == '1'
config_srvs = node['concourse-ci']['docker-compose']['config']
override_config_srvs = node.override['concourse-ci']['docker-compose']['config']
force_override_config_srvs = node.force_override['concourse-ci']['docker-compose']['config']
if config_format_version == '2'
  config_srvs = config_srvs['services']
  override_config_srvs = override_config_srvs['services']
  force_override_config_srvs = force_override_config_srvs['services']
end

# Database
db_envs_org = config_srvs['concourse-db']['environment']
db_envs = {}
db_vols = config_srvs['concourse-db']['volumes'].to_a

db_password_reset = node['concourse-ci']['docker-compose']['db_password_reset']
db_passwd = nil
db_password_vault_item = node['concourse-ci']['docker-compose']['db_password_vault_item']
if !db_password_vault_item.empty?
  # 1. from Chef Vault (recommended).
  db_passwd = get_vault_item_value(db_password_vault_item)
else
  # 2. from Chef attribute (NOT recommended).
  db_passwd = db_envs_org['POSTGRES_PASSWORD']
  if db_passwd.nil? || db_passwd.empty?
    db_passwd = \
      if !config_srvs_local.nil? \
        && config_srvs_local['concourse-db']['environment']['POSTGRES_PASSWORD'] != '${POSTGRES_PASSWORD}' \
        && !db_password_reset
        # 3. preserve it from the local docker-compose.yml file for backward compatibility.
        config_srvs_local['concourse-db']['environment']['POSTGRES_PASSWORD']
      elsif !env_local.nil? && !env_local['POSTGRES_PASSWORD'].nil? && !db_password_reset
        # 4. preserve it from the local .env file.
        env_local['POSTGRES_PASSWORD']
      else
        # 5. auto generate.
        SecureRandom.hex  # or urlsafe_base64
      end
  end
end
# prevent Chef from logging password attribute value. (=> template variables)
db_envs['POSTGRES_PASSWORD'] = '${POSTGRES_PASSWORD}'

db_vols.push("#{pgdata_dir}:#{db_envs_org['PGDATA']}") if !pgdata_dir.nil? && !pgdata_dir.empty?

# merge environment hash
force_override_config_srvs['concourse-db']['environment'] = db_envs unless db_envs.empty?
# reset vlumes array.
override_config_srvs['concourse-db']['volumes'] = db_vols unless db_vols.empty?

# Web
web_envs_org = config_srvs['concourse-web']['environment']
web_envs = {}
web_vols = config_srvs['concourse-web']['volumes'].to_a

web_ports = config_srvs['concourse-web']['ports']
override_config_srvs['concourse-web']['ports'] = ['8080:8080'] if web_ports.empty?

web_vols.push("#{node['concourse-ci']['docker-compose']['web_keys_dir']}:/concourse-keys")

encryption_key = nil
encryption_key_vault_item = node['concourse-ci']['docker-compose']['web_encryption_key_vault_item']
unless encryption_key_vault_item.empty?
  encryption_key = get_vault_item_value(encryption_key_vault_item)
  web_envs['CONCOURSE_ENCRYPTION_KEY'] = '${CONCOURSE_ENCRYPTION_KEY}'
end

web_password_reset = node['concourse-ci']['docker-compose']['web_password_reset']
basic_auth_passwd = nil
web_password_vault_item = node['concourse-ci']['docker-compose']['web_password_vault_item']
if !web_password_vault_item.empty?
  # 1. from Chef Vault (recommended).
  basic_auth_passwd = get_vault_item_value(web_password_vault_item)
else
  # 2. from Chef attribute (NOT recommended).
  basic_auth_passwd = web_envs_org['CONCOURSE_BASIC_AUTH_PASSWORD']
  if basic_auth_passwd.nil? || basic_auth_passwd.empty?
    basic_auth_passwd = \
      if !config_srvs_local.nil? \
        && config_srvs_local['concourse-web']['environment']['CONCOURSE_BASIC_AUTH_PASSWORD'] != '${CONCOURSE_BASIC_AUTH_PASSWORD}' \
        && !web_password_reset
        # 3. preserve it from the local docker-compose.yml file for backward compatibility.
        config_srvs_local['concourse-web']['environment']['CONCOURSE_BASIC_AUTH_PASSWORD']
      elsif !env_local.nil? && !env_local['CONCOURSE_BASIC_AUTH_PASSWORD'].nil? && !web_password_reset
        # 4. preserve it from the local .env file.
        env_local['CONCOURSE_BASIC_AUTH_PASSWORD']
      else
        # 5. auto generate.
        SecureRandom.hex  # or urlsafe_base64
      end
  end
end
# prevent Chef from logging password attribute value. (=> template variables)
web_envs['CONCOURSE_BASIC_AUTH_PASSWORD'] = '${CONCOURSE_BASIC_AUTH_PASSWORD}'

oauth_client_id = nil
oauth_client_id_vault_item = node['concourse-ci']['docker-compose']['web_oauth_client_id_vault_item']
unless oauth_client_id_vault_item.empty?
  oauth_client_id = get_vault_item_value(oauth_client_id_vault_item)
  web_envs['CONCOURSE_GENERIC_OAUTH_CLIENT_ID'] = '${CONCOURSE_GENERIC_OAUTH_CLIENT_ID}'
end

oauth_client_secret = nil
oauth_client_secret_vault_item = node['concourse-ci']['docker-compose']['web_oauth_client_secret_vault_item']
unless oauth_client_secret_vault_item.empty?
  oauth_client_secret = get_vault_item_value(oauth_client_secret_vault_item)
  web_envs['CONCOURSE_GENERIC_OAUTH_CLIENT_SECRET'] = '${CONCOURSE_GENERIC_OAUTH_CLIENT_SECRET}'
end

external_url = web_envs_org['CONCOURSE_EXTERNAL_URL']
web_envs['CONCOURSE_EXTERNAL_URL'] = "http://#{node['ipaddress']}:8080" if external_url.nil?

data_source = web_envs_org['CONCOURSE_POSTGRES_DATA_SOURCE']
# for backward compatibility.
data_source = data_source.gsub(/<POSTGRES_PASSWORD>/, '${POSTGRES_PASSWORD}')
web_envs['CONCOURSE_POSTGRES_DATA_SOURCE'] = data_source

template "#{bin_dir}/concourse_up" do
  source 'opt/docker-compose/app/concourse/bin/concourse_up'
  owner 'root'
  group 'root'
  mode '0755'
  action :create
end

if node['concourse-ci']['with_ssl_cert_cookbook']
  include_recipe 'ssl_cert::server_key_pairs'
  ::Chef::Recipe.send(:include, SSLCert::Helper)

  cn = node['concourse-ci']['ssl_cert']['common_name']
  # Concourse web process owner is root.
  web_vols.push("#{server_cert_path(cn)}:/root/server.crt:ro")
  web_vols.push("#{server_key_path(cn)}:/root/server.key:ro")
  web_envs['CONCOURSE_TLS_CERT'] = '/root/server.crt'
  web_envs['CONCOURSE_TLS_KEY'] = '/root/server.key'
end

# Worker
worker_vols = config_srvs['concourse-worker']['volumes'].to_a
worker_vols.push("#{node['concourse-ci']['docker-compose']['worker_keys_dir']}:/concourse-keys")

# Common
if node['concourse-ci']['docker-compose']['import_ca']
  include_recipe 'ssl_cert::ca_certs'
  ::Chef::Recipe.send(:include, SSLCert::Helper)

  node['concourse-ci']['ssl_cert']['ca_names'].each {|ca_name|
    ca_cert_vol = "#{ca_cert_path(ca_name)}:/usr/share/ca-certificates/#{ca_name}.crt:ro"
    web_vols.push(ca_cert_vol)
    worker_vols.push(ca_cert_vol)
  }

  import_ca_script = '/usr/local/bin/concourse_import_ca'
  template "#{bin_dir}/concourse_import_ca" do
    source 'opt/docker-compose/app/concourse/bin/concourse_import_ca'
    owner 'root'
    group 'root'
    mode '0755'
    action :create
  end
  import_ca_script_vol = "#{bin_dir}/concourse_import_ca:#{import_ca_script}:ro"
  web_vols.push(import_ca_script_vol)
  worker_vols.push(import_ca_script_vol)

  image_entrypoint = node['concourse-ci']['docker-image']['entrypoint']
  override_config_srvs['concourse-web']['entrypoint'] \
    = "/bin/sh -c \"#{import_ca_script} && #{image_entrypoint} web\""
  override_config_srvs['concourse-worker']['entrypoint'] \
    = "/bin/sh -c \"#{import_ca_script} && #{image_entrypoint} worker\""
  if config_format_version == '2'
    node.rm('concourse-ci', 'docker-compose', 'config', 'services', 'concourse-web', 'command')
    node.rm('concourse-ci', 'docker-compose', 'config', 'services', 'concourse-worker', 'command')
  else
    node.rm('concourse-ci', 'docker-compose', 'config', 'concourse-web', 'command')
    node.rm('concourse-ci', 'docker-compose', 'config', 'concourse-worker', 'command')
  end
end

# merge environment hash
force_override_config_srvs['concourse-web']['environment'] = web_envs unless web_envs.empty?
# reset vlumes array.
override_config_srvs['concourse-web']['volumes'] = web_vols unless web_vols.empty?
override_config_srvs['concourse-worker']['volumes'] = worker_vols unless worker_vols.empty?

template env_file do
  source 'opt/docker-compose/app/concourse/.env'
  owner 'root'
  group 'root'
  mode '0600'
  sensitive true
  # prevent Chef from logging password attribute value.
  variables(
    # secrets
    db_passwd: db_passwd,
    encryption_key: encryption_key,
    basic_auth_passwd: basic_auth_passwd,
    oauth_client_id: oauth_client_id,
    oauth_client_secret: oauth_client_secret
  )
end

template config_file do
  source  'opt/docker-compose/app/concourse/docker-compose.yml'
  owner 'root'
  group 'root'
  mode '0600'
end

log <<-"EOM"
Note: You must execute the following command manually.
  See #{doc_url}
  - Start:
    $ cd #{app_dir}
    $ sudo docker-compose up
  - Stop
    $ sudo docker-compose down
EOM
