configure_environment.rb 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/env ruby
  2. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  3. require 'yaml'
  4. require 'resolv'
  5. require 'fileutils'
  6. #
  7. # Configures the CI system
  8. # - (randomly) mysql or postgresql, if available
  9. # - (randomly) Redis or File as web socket session back end, if Redis is available
  10. # - (randomly) Memcached or File as Rails cache store, if Memcached is available
  11. # - Elasticsearch support, if available
  12. #
  13. # Database config happens directly in config/database.yml, other settings are written to
  14. # .gitlab/environment.env which must be sourced in the CI configuration.
  15. #
  16. class ConfigureEnvironment
  17. @env_file_content = <<~ENV_FILE_CONTENT
  18. #!/bin/bash
  19. FRESHENVFILE=fresh.env && test -f $FRESHENVFILE && source $FRESHENVFILE
  20. true
  21. ENV_FILE_CONTENT
  22. DB_SETTINGS_MAP = {
  23. 'postgresql' => {
  24. 'adapter' => 'postgresql',
  25. 'username' => 'zammad',
  26. 'password' => 'zammad',
  27. 'host' => 'postgresql', # db alias from gitlab-ci.yml
  28. },
  29. 'mysql' => {
  30. 'adapter' => 'mysql2',
  31. 'username' => 'root',
  32. 'password' => 'zammad',
  33. 'host' => 'mysql', # db alias from gitlab-ci.yml
  34. }
  35. }.freeze
  36. # Detect service availability based on host presence in network.
  37. def self.network_host_exists?(hostname)
  38. # GitLab used the /etc/hosts file if FF_NETWORK_PER_BUILD is not set.
  39. return true if File.foreach('/etc/hosts').any? { |l| l[hostname] }
  40. # Fall back to DNS lookup, also for GitHub
  41. !!Resolv::DNS.new.tap { |dns| dns.timeouts = 3 }.getaddress(hostname)
  42. rescue Resolv::ResolvError
  43. false
  44. end
  45. def self.configure_database # rubocop:disable Metrics/AbcSize
  46. if File.exist? File.join(__dir__, '../config/database.yml')
  47. puts "'config/database.yml' already exists and will not be changed."
  48. return
  49. end
  50. # Ruby 3.1 uses Psych 4 which made aliases support optional
  51. cnf = YAML.load_file(File.join(__dir__, '../config/database/database.yml'), aliases: true)
  52. cnf.delete('default')
  53. database = ENV['ENFORCE_DB_SERVICE'] || %w[postgresql mysql].shuffle.find do |db|
  54. network_host_exists?(db)
  55. end
  56. raise "Can't find any supported database." if database.nil?
  57. puts "Using #{database} as database service."
  58. # fetch DB settings from settings map and fallback to postgresql
  59. db_settings = DB_SETTINGS_MAP.fetch(database) { DB_SETTINGS_MAP['postgresql'] }
  60. %w[development test production].each do |environment|
  61. cnf[environment].merge!(db_settings)
  62. end
  63. File.write(File.join(__dir__, '../config/database.yml'), Psych.dump(cnf))
  64. end
  65. def self.configure_redis
  66. has_redis = network_host_exists?('redis')
  67. needs_redis = !%w[1 true].include?(ENV['ZAMMAD_SAFE_MODE']) # rubocop:disable Rails/NegateInclude
  68. if needs_redis && !has_redis
  69. raise 'Redis was not found, but is required for ActionCable.'
  70. end
  71. if has_redis && [true, needs_redis].sample
  72. puts 'Using Redis as adapter for ActionCable.'
  73. @env_file_content += "export REDIS_URL='redis://redis:6379'\n"
  74. if [true, false].sample
  75. puts 'Using FS as web socket session store.'
  76. @env_file_content += "export ZAMMAD_WEBSOCKET_SESSION_STORE_FORCE_FS_BACKEND='true'\n"
  77. else
  78. puts 'Using Redis as web socket session store.'
  79. end
  80. return
  81. end
  82. puts 'Not using Redis.'
  83. @env_file_content += "unset REDIS_URL\n"
  84. end
  85. def self.configure_memcached
  86. if network_host_exists?('memcached') && [true, false].sample
  87. puts 'Using memcached as Rails cache store.'
  88. @env_file_content += "export MEMCACHE_SERVERS='memcached'\n"
  89. return
  90. end
  91. puts "Using Zammad's file store as Rails cache store."
  92. @env_file_content += "unset MEMCACHE_SERVERS\n"
  93. end
  94. def self.configure_elasticsearch
  95. if network_host_exists?('elasticsearch')
  96. puts 'Activating support for Elasticsearch.'
  97. @env_file_content += "export ES_URL='http://elasticsearch:9200'\n"
  98. return
  99. end
  100. puts 'Not using Elasticsearch.'
  101. @env_file_content += "unset ES_URL\n"
  102. end
  103. # Since configure_database skips if database.yml already exists, check the
  104. # content of that file to reliably determine the database type in all cases.
  105. def self.database_type
  106. database = File.read(File.join(__dir__, '../config/database.yml')).match(%r{^\s*adapter:\s*(mysql|postgresql)})[1]
  107. if !database
  108. raise 'Could not determine database type, cannot setup cable.yml'
  109. end
  110. database
  111. end
  112. def self.write_env_file
  113. File.write(File.join(__dir__, 'environment.env'), @env_file_content)
  114. end
  115. def self.run
  116. configure_database
  117. configure_redis
  118. configure_memcached
  119. configure_elasticsearch
  120. write_env_file
  121. end
  122. end
  123. ConfigureEnvironment.run