Merge branch '14812-wb1-new-config' refs #14812
[arvados.git] / services / api / config / arvados_config.rb
index 8817b8b10fb20c869d6a59f4d8ec688c07db5917..cf4b842c4a93cef5900c66aa67cbfd259bb031db 100644 (file)
@@ -17,6 +17,7 @@
 # delete application.yml and database.yml.
 
 require 'config_loader'
+require 'open3'
 
 begin
   # If secret_token.rb exists here, we need to load it first.
@@ -42,23 +43,38 @@ EOS
   WARNED_OMNIAUTH_CONFIG = true
 end
 
-$arvados_config = {}
+# Load the defaults, used by config:migrate and fallback loading
+# legacy application.yml
+Open3.popen2("arvados-server", "config-dump", "-config=-") do |stdin, stdout, status_thread|
+  stdin.write("Clusters: {xxxxx: {}}")
+  stdin.close
+  confs = YAML.load(stdout, deserialize_symbols: false)
+  clusterID, clusterConfig = confs["Clusters"].first
+  $arvados_config_defaults = clusterConfig
+  $arvados_config_defaults["ClusterID"] = clusterID
+end
 
-["#{::Rails.root.to_s}/config/config.default.yml", "/etc/arvados/config.yml"].each do |path|
-  if File.exist? path
-    confs = YAML.load(IO.read(path), deserialize_symbols: false)
-    if confs
-      clusters = confs["Clusters"].first
-      $arvados_config["ClusterID"] = clusters[0]
-      $arvados_config.deep_merge!(clusters[1])
-    end
+# Load the global config file
+Open3.popen2("arvados-server", "config-dump") do |stdin, stdout, status_thread|
+  confs = YAML.load(stdout, deserialize_symbols: false)
+  if confs && !confs.empty?
+    # config-dump merges defaults with user configuration, so every
+    # key should be set.
+    clusterID, clusterConfig = confs["Clusters"].first
+    $arvados_config_global = clusterConfig
+    $arvados_config_global["ClusterID"] = clusterID
+  else
+    # config-dump failed, assume we will be loading from legacy
+    # application.yml, initialize with defaults.
+    $arvados_config_global = $arvados_config_defaults.deep_dup
   end
 end
 
-$base_arvados_config = $arvados_config.deep_dup
+# Now make a copy
+$arvados_config = $arvados_config_global.deep_dup
 
+# Declare all our configuration items.
 arvcfg = ConfigLoader.new
-
 arvcfg.declare_config "ClusterID", NonemptyString, :uuid_prefix
 arvcfg.declare_config "ManagementToken", String, :ManagementToken
 arvcfg.declare_config "Git.Repositories", String, :git_repositories_dir
@@ -95,8 +111,8 @@ arvcfg.declare_config "Collections.CollectionVersioning", Boolean, :collection_v
 arvcfg.declare_config "Collections.PreserveVersionIfIdle", ActiveSupport::Duration, :preserve_version_if_idle
 arvcfg.declare_config "Collections.TrashSweepInterval", ActiveSupport::Duration, :trash_sweep_interval
 arvcfg.declare_config "Collections.BlobSigningKey", NonemptyString, :blob_signing_key
-arvcfg.declare_config "Collections.BlobSigningTTL", Integer, :blob_signature_ttl
-arvcfg.declare_config "Collections.BlobSigning", Boolean, :permit_create_collection_with_unsigned_manifest
+arvcfg.declare_config "Collections.BlobSigningTTL", ActiveSupport::Duration, :blob_signature_ttl
+arvcfg.declare_config "Collections.BlobSigning", Boolean, :permit_create_collection_with_unsigned_manifest, ->(cfg, k, v) { ConfigLoader.set_cfg cfg, "Collections.BlobSigning", !v }
 arvcfg.declare_config "Containers.SupportedDockerImageFormats", Array, :docker_image_formats
 arvcfg.declare_config "Containers.LogReuseDecisions", Boolean, :log_reuse_decisions
 arvcfg.declare_config "Containers.DefaultKeepCacheRAM", Integer, :container_default_keep_cache_ram
@@ -130,21 +146,28 @@ arvcfg.declare_config "Containers.JobsAPI.ReuseJobIfOutputsDiffer", Boolean, :re
 arvcfg.declare_config "Containers.JobsAPI.DefaultDockerImage", String, :default_docker_image_for_jobs
 arvcfg.declare_config "Mail.MailchimpAPIKey", String, :mailchimp_api_key
 arvcfg.declare_config "Mail.MailchimpListID", String, :mailchimp_list_id
+arvcfg.declare_config "Services.Controller.ExternalURL", URI
 arvcfg.declare_config "Services.Workbench1.ExternalURL", URI, :workbench_address
 arvcfg.declare_config "Services.Websocket.ExternalURL", URI, :websocket_address
 arvcfg.declare_config "Services.WebDAV.ExternalURL", URI, :keep_web_service_url
 arvcfg.declare_config "Services.GitHTTP.ExternalURL", URI, :git_repo_https_base
 arvcfg.declare_config "Services.GitSSH.ExternalURL", URI, :git_repo_ssh_base, ->(cfg, k, v) { ConfigLoader.set_cfg cfg, "Services.GitSSH.ExternalURL", "ssh://#{v}" }
 arvcfg.declare_config "RemoteClusters", Hash, :remote_hosts, ->(cfg, k, v) {
-  h = {}
+  h = if cfg["RemoteClusters"] then
+        cfg["RemoteClusters"].deep_dup
+      else
+        {}
+      end
   v.each do |clusterid, host|
-    h[clusterid] = {
-      "Host" => host,
-      "Proxy" => true,
-      "Scheme" => "https",
-      "Insecure" => false,
-      "ActivateUsers" => false
-    }
+    if h[clusterid].nil?
+      h[clusterid] = {
+        "Host" => host,
+        "Proxy" => true,
+        "Scheme" => "https",
+        "Insecure" => false,
+        "ActivateUsers" => false
+      }
+    end
   end
   ConfigLoader.set_cfg cfg, "RemoteClusters", h
 }
@@ -153,30 +176,28 @@ arvcfg.declare_config "RemoteClusters.*.Proxy", Boolean, :remote_hosts_via_dns
 dbcfg = ConfigLoader.new
 
 dbcfg.declare_config "PostgreSQL.ConnectionPool", Integer, :pool
-dbcfg.declare_config "PostgreSQL.Connection.Host", String, :host
-dbcfg.declare_config "PostgreSQL.Connection.Port", Integer, :port
-dbcfg.declare_config "PostgreSQL.Connection.User", String, :username
-dbcfg.declare_config "PostgreSQL.Connection.Password", String, :password
-dbcfg.declare_config "PostgreSQL.Connection.DBName", String, :database
-dbcfg.declare_config "PostgreSQL.Connection.Template", String, :template
-dbcfg.declare_config "PostgreSQL.Connection.Encoding", String, :encoding
+dbcfg.declare_config "PostgreSQL.Connection.host", String, :host
+dbcfg.declare_config "PostgreSQL.Connection.port", String, :port
+dbcfg.declare_config "PostgreSQL.Connection.user", String, :username
+dbcfg.declare_config "PostgreSQL.Connection.password", String, :password
+dbcfg.declare_config "PostgreSQL.Connection.dbname", String, :database
+dbcfg.declare_config "PostgreSQL.Connection.template", String, :template
+dbcfg.declare_config "PostgreSQL.Connection.encoding", String, :encoding
 
 application_config = {}
 %w(application.default application).each do |cfgfile|
   path = "#{::Rails.root.to_s}/config/#{cfgfile}.yml"
-  if File.exist? path
-    confs = ConfigLoader.load(path)
-    # Ignore empty YAML file:
-    next if confs == false
-    application_config.deep_merge!(confs['common'] || {})
-    application_config.deep_merge!(confs[::Rails.env.to_s] || {})
-  end
+  confs = ConfigLoader.load(path, erb: true)
+  # Ignore empty YAML file:
+  next if confs == false
+  application_config.deep_merge!(confs['common'] || {})
+  application_config.deep_merge!(confs[::Rails.env.to_s] || {})
 end
 
 db_config = {}
 path = "#{::Rails.root.to_s}/config/database.yml"
 if File.exist? path
-  db_config = ConfigLoader.load(path)
+  db_config = ConfigLoader.load(path, erb: true)
 end
 
 $remaining_config = arvcfg.migrate_config(application_config, $arvados_config)
@@ -190,11 +211,28 @@ if application_config[:auto_activate_users_from]
   end
 end
 
-# Checks for wrongly typed configuration items, and essential items
-# that can't be empty
-arvcfg.coercion_and_check $base_arvados_config, check_nonempty: false
-arvcfg.coercion_and_check $arvados_config
-dbcfg.coercion_and_check $arvados_config
+if application_config[:host] || application_config[:port] || application_config[:scheme]
+  if !application_config[:host] || application_config[:host].empty?
+    raise "Must set 'host' when setting 'port' or 'scheme'"
+  end
+  $arvados_config.Services["Controller"]["ExternalURL"] = URI((application_config[:scheme] || "https")+"://"+application_config[:host]+
+                                                              (if application_config[:port] then ":#{application_config[:port]}" else "" end))
+end
+
+# Checks for wrongly typed configuration items, coerces properties
+# into correct types (such as Duration), and optionally raise error
+# for essential configuration that can't be empty.
+arvcfg.coercion_and_check $arvados_config_defaults, check_nonempty: false
+arvcfg.coercion_and_check $arvados_config_global, check_nonempty: false
+arvcfg.coercion_and_check $arvados_config, check_nonempty: true
+dbcfg.coercion_and_check $arvados_config, check_nonempty: true
+
+# * $arvados_config_defaults is the defaults
+# * $arvados_config_global is $arvados_config_defaults merged with the contents of /etc/arvados/config.yml
+# These are used by the rake config: tasks
+#
+# * $arvados_config is $arvados_config_global merged with the migrated contents of application.yml
+# This is what actually gets copied into the Rails configuration object.
 
 if $arvados_config["Collections"]["DefaultTrashLifetime"] < 86400.seconds then
   raise "default_trash_lifetime is %d, must be at least 86400" % Rails.configuration.Collections.DefaultTrashLifetime
@@ -206,16 +244,16 @@ end
 # rails environments.
 #
 if ::Rails.env.to_s == "test" && db_config["test"].nil?
-  $arvados_config["PostgreSQL"]["Connection"]["DBName"] = "arvados_test"
+  $arvados_config["PostgreSQL"]["Connection"]["dbname"] = "arvados_test"
 end
 
-if $arvados_config["PostgreSQL"]["Connection"]["Password"].empty?
+if $arvados_config["PostgreSQL"]["Connection"]["password"].empty?
   raise "Database password is empty, PostgreSQL section is: #{$arvados_config["PostgreSQL"]}"
 end
 
-dbhost = $arvados_config["PostgreSQL"]["Connection"]["Host"]
-if $arvados_config["PostgreSQL"]["Connection"]["Post"] != 0
-  dbhost += ":#{$arvados_config["PostgreSQL"]["Connection"]["Post"]}"
+dbhost = $arvados_config["PostgreSQL"]["Connection"]["host"]
+if $arvados_config["PostgreSQL"]["Connection"]["port"] != 0
+  dbhost += ":#{$arvados_config["PostgreSQL"]["Connection"]["port"]}"
 end
 
 #
@@ -224,15 +262,19 @@ end
 # For config migration, we've previously populated the PostgreSQL
 # section of the config from database.yml
 #
-ENV["DATABASE_URL"] = "postgresql://#{$arvados_config["PostgreSQL"]["Connection"]["User"]}:"+
-                      "#{$arvados_config["PostgreSQL"]["Connection"]["Password"]}@"+
-                      "#{dbhost}/#{$arvados_config["PostgreSQL"]["Connection"]["DBName"]}?"+
-                      "template=#{$arvados_config["PostgreSQL"]["Connection"]["Template"]}&"+
+ENV["DATABASE_URL"] = "postgresql://#{$arvados_config["PostgreSQL"]["Connection"]["user"]}:"+
+                      "#{$arvados_config["PostgreSQL"]["Connection"]["password"]}@"+
+                      "#{dbhost}/#{$arvados_config["PostgreSQL"]["Connection"]["dbname"]}?"+
+                      "template=#{$arvados_config["PostgreSQL"]["Connection"]["template"]}&"+
                       "encoding=#{$arvados_config["PostgreSQL"]["Connection"]["client_encoding"]}&"+
                       "pool=#{$arvados_config["PostgreSQL"]["ConnectionPool"]}"
 
 Server::Application.configure do
+  # Copy into the Rails config object.  This also turns Hash into
+  # OrderedOptions so that application code can use
+  # Rails.configuration.API.Blah instead of
+  # Rails.configuration.API["Blah"]
   ConfigLoader.copy_into_config $arvados_config, config
   ConfigLoader.copy_into_config $remaining_config, config
-  config.secret_key_base = config.secret_token
+  secrets.secret_key_base = $arvados_config["API"]["RailsSessionSecretToken"]
 end