# Skip require_thread_api_token if this is a show action
# for an object uuid that supports anonymous access.
skip_around_action :require_thread_api_token, if: proc { |ctrl|
- Rails.configuration.anonymous_user_token and
+ !Rails.configuration.Users.AnonymousUserToken.empty? and
'show' == ctrl.action_name and
params['uuid'] and
model_class.in?([Collection, Group, Job, PipelineInstance, PipelineTemplate])
include ActionController::Live
skip_around_action :require_thread_api_token, if: proc { |ctrl|
- Rails.configuration.anonymous_user_token and
+ !Rails.configuration.Users.AnonymousUserToken.empty? and
'show' == ctrl.action_name
}
skip_around_action(:require_thread_api_token,
# Otherwise, it's impossible to know whether any other request succeeded
# because of the reader token.
coll = nil
- tokens = [(Rails.configuration.anonymous_user_token || nil),
+ tokens = [(if !Rails.configuration.Users.AnonymousUserToken.empty? then
+ Rails.configuration.Users.AnonymousUserToken else nil end),
params[:reader_token],
Thread.current[:arvados_api_token]].compact
usable_token = find_usable_token(tokens) do
opts = {}
if usable_token == params[:reader_token]
opts[:path_token] = usable_token
- elsif usable_token == Rails.configuration.anonymous_user_token
+ elsif usable_token == Rails.configuration.Users.AnonymousUserToken
# Don't pass a token at all
else
# We pass the current user's real token only if it's necessary
class JobsController < ApplicationController
skip_around_action :require_thread_api_token, if: proc { |ctrl|
- Rails.configuration.anonymous_user_token and
+ !Rails.configuration.Users.AnonymousUserToken.empty? and
'show' == ctrl.action_name
}
end
def webshell
- return render_not_found if not Rails.configuration.shell_in_a_box_url
- @webshell_url = Rails.configuration.shell_in_a_box_url % {
+ return render_not_found if Rails.configuration.Workbench.ShellInABoxURL.empty?
+ @webshell_url = Rails.configuration.Workbench.ShellInABoxURL % {
uuid: @object.uuid,
hostname: @object.hostname,
}
class WorkUnitsController < ApplicationController
skip_around_action :require_thread_api_token, if: proc { |ctrl|
- Rails.configuration.anonymous_user_token and
+ !Rails.configuration.Users.AnonymousUserToken.empty? and
'show_child_component' == ctrl.action_name
}
end
def current_api_host
- Rails.configuration.arvados_v1_base.gsub(/https?:\/\/|\/arvados\/v1/, '')
+ "#{Rails.configuration.Services.Controller.ExternalURL.hostname}:#{Rails.configuration.Services.Controller.ExternalURL.port}"
end
def current_uuid_prefix
- current_api_host[0..4]
+ Rails.configuration.ClusterID
end
def render_markup(markup)
elsif (file_type.raw_media_type == "text") || (file_type.raw_media_type == "image")
true
elsif (file_type.raw_media_type == "application") &&
- (Rails.configuration.application_mimetypes_with_view_icon.include? (file_type.sub_type))
+ Rails.configuration.Workbench.Workbench.ApplicationMimetypesWithViewIcon[file_type.sub_type]
true
else
false
module ArvadosWorkbench
class Application < Rails::Application
+
+ require_relative "arvados_config.rb"
+
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+#
+# Load Arvados configuration from /etc/arvados/config.yml, using defaults
+# from config.default.yml
+#
+# Existing application.yml is migrated into the new config structure.
+# Keys in the legacy application.yml take precedence.
+#
+# Use "bundle exec config:dump" to get the complete active configuration
+#
+# Use "bundle exec config:migrate" to migrate application.yml to
+# config.yml. After adding the output of config:migrate to
+# /etc/arvados/config.yml, you will be able to delete application.yml.
+
+require 'config_loader'
+
+begin
+ # If secret_token.rb exists here, we need to load it first.
+ require_relative 'secret_token.rb'
+rescue LoadError
+ # Normally secret_token.rb is missing and the secret token is
+ # configured by application.yml (i.e., here!) instead.
+end
+
+# Load the defaults
+$arvados_config_defaults = ConfigLoader.load "#{::Rails.root.to_s}/config/config.default.yml"
+if $arvados_config_defaults.empty?
+ raise "Missing #{::Rails.root.to_s}/config/config.default.yml"
+end
+
+def remove_sample_entries(h)
+ return unless h.is_a? Hash
+ h.delete("SAMPLE")
+ h.each { |k, v| remove_sample_entries(v) }
+end
+remove_sample_entries($arvados_config_defaults)
+
+clusterID, clusterConfig = $arvados_config_defaults["Clusters"].first
+$arvados_config_defaults = clusterConfig
+$arvados_config_defaults["ClusterID"] = clusterID
+
+# Initialize the global config with the defaults
+$arvados_config_global = $arvados_config_defaults.deep_dup
+
+# Load the global config file
+confs = ConfigLoader.load "/etc/arvados/config.yml"
+if !confs.empty?
+ clusterID, clusterConfig = confs["Clusters"].first
+ $arvados_config_global["ClusterID"] = clusterID
+
+ # Copy the cluster config over the defaults
+ $arvados_config_global.deep_merge!(clusterConfig)
+end
+
+# Now make a copy
+$arvados_config = $arvados_config_global.deep_dup
+
+# Declare all our configuration items.
+arvcfg = ConfigLoader.new
+
+arvcfg.declare_config "ManagementToken", String, :ManagementToken
+arvcfg.declare_config "TLS.Insecure", Boolean, :arvados_insecure_https
+
+arvcfg.declare_config "Services.Controller.ExternalURL", URI, :arvados_v1_base, ->(cfg, k, v) {
+ u = URI(v)
+ u.path = ""
+ ConfigLoader.set_cfg cfg, "Services.Controller.ExternalURL", u
+}
+
+arvcfg.declare_config "Services.WebShell.ExternalURL", URI, :shell_in_a_box_url, ->(cfg, k, v) {
+ v ||= ""
+ u = URI(v.sub("%{hostname}", "*"))
+ u.path = ""
+ ConfigLoader.set_cfg cfg, "Services.WebShell.ExternalURL", u
+}
+
+arvcfg.declare_config "Services.WebDAV.ExternalURL", URI, :keep_web_service_url, ->(cfg, k, v) {
+ v ||= ""
+ u = URI(v.sub("%{uuid_or_pdh}", "*"))
+ u.path = ""
+ ConfigLoader.set_cfg cfg, "Services.WebDAV.ExternalURL", u
+}
+
+arvcfg.declare_config "Services.WebDAVDownload.ExternalURL", URI, :keep_web_download_url, ->(cfg, k, v) {
+ v ||= ""
+ u = URI(v.sub("%{uuid_or_pdh}", "*"))
+ u.path = ""
+ ConfigLoader.set_cfg cfg, "Services.WebDAVDownload.ExternalURL", u
+}
+
+arvcfg.declare_config "Services.Composer.ExternalURL", URI, :composer_url
+arvcfg.declare_config "Services.Workbench2.ExternalURL", URI, :workbench2_url
+
+arvcfg.declare_config "Workbench.ApplicationMimetypesWithViewIcon", Hash, :application_mimetypes_with_view_icon, ->(cfg, k, v) {
+ mimetypes = {}
+ v.each do |m|
+ mimetypes[m] = {}
+ end
+ ConfigLoader.set_cfg cfg, "Workbench.ApplicationMimetypesWithViewIcon", mimetypes
+}
+
+arvcfg.declare_config "Users.AnonymousUserToken", String, :anonymous_user_token
+
+
+application_config = {}
+%w(application.default application).each do |cfgfile|
+ path = "#{::Rails.root.to_s}/config/#{cfgfile}.yml"
+ 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
+
+$remaining_config = arvcfg.migrate_config(application_config, $arvados_config)
+
+# 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
+
+# * $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.
+
+ArvadosWorkbench::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
+ secrets.secret_key_base = $arvados_config["Workbench"]["SecretToken"]
+end
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+module Psych
+ module Visitors
+ class YAMLTree < Psych::Visitors::Visitor
+ def visit_ActiveSupport_Duration o
+ seconds = o.to_i
+ outstr = ""
+ if seconds / 3600 > 0
+ outstr += "#{seconds / 3600}h"
+ seconds = seconds % 3600
+ end
+ if seconds / 60 > 0
+ outstr += "#{seconds / 60}m"
+ seconds = seconds % 60
+ end
+ if seconds > 0
+ outstr += "#{seconds}s"
+ end
+ if outstr == ""
+ outstr = "0s"
+ end
+ @emitter.scalar outstr, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+
+ def visit_URI_Generic o
+ @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+
+ def visit_URI_HTTP o
+ @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+
+ def visit_Pathname o
+ @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+ end
+ end
+end
+
+
+module Boolean; end
+class TrueClass; include Boolean; end
+class FalseClass; include Boolean; end
+
+class NonemptyString < String
+end
+
+class ConfigLoader
+ def initialize
+ @config_migrate_map = {}
+ @config_types = {}
+ end
+
+ def declare_config(assign_to, configtype, migrate_from=nil, migrate_fn=nil)
+ if migrate_from
+ @config_migrate_map[migrate_from] = migrate_fn || ->(cfg, k, v) {
+ ConfigLoader.set_cfg cfg, assign_to, v
+ }
+ end
+ @config_types[assign_to] = configtype
+ end
+
+
+ def migrate_config from_config, to_config
+ remainders = {}
+ from_config.each do |k, v|
+ if @config_migrate_map[k.to_sym]
+ @config_migrate_map[k.to_sym].call to_config, k, v
+ else
+ remainders[k] = v
+ end
+ end
+ remainders
+ end
+
+ def coercion_and_check check_cfg, check_nonempty: true
+ @config_types.each do |cfgkey, cfgtype|
+ begin
+ cfg = check_cfg
+ k = cfgkey
+ ks = k.split '.'
+ k = ks.pop
+ ks.each do |kk|
+ cfg = cfg[kk]
+ if cfg.nil?
+ break
+ end
+ end
+
+ if cfg.nil?
+ raise "missing #{cfgkey}"
+ end
+
+ if cfgtype == String and !cfg[k]
+ cfg[k] = ""
+ end
+
+ if cfgtype == String and cfg[k].is_a? Symbol
+ cfg[k] = cfg[k].to_s
+ end
+
+ if cfgtype == Pathname and cfg[k].is_a? String
+
+ if cfg[k] == ""
+ cfg[k] = Pathname.new("")
+ else
+ cfg[k] = Pathname.new(cfg[k])
+ if !cfg[k].exist?
+ raise "#{cfgkey} path #{cfg[k]} does not exist"
+ end
+ end
+ end
+
+ if cfgtype == NonemptyString
+ if (!cfg[k] || cfg[k] == "") && check_nonempty
+ raise "#{cfgkey} cannot be empty"
+ end
+ if cfg[k].is_a? String
+ next
+ end
+ end
+
+ if cfgtype == ActiveSupport::Duration
+ if cfg[k].is_a? Integer
+ cfg[k] = cfg[k].seconds
+ elsif cfg[k].is_a? String
+ cfg[k] = ConfigLoader.parse_duration(cfg[k], cfgkey: cfgkey)
+ end
+ end
+
+ if cfgtype == URI
+ if cfg[k]
+ cfg[k] = URI(cfg[k])
+ else
+ cfg[k] = URI("")
+ end
+ end
+
+ if cfgtype == Integer && cfg[k].is_a?(String)
+ v = cfg[k].sub(/B\s*$/, '')
+ if mt = /(-?\d*\.?\d+)\s*([KMGTPE]i?)$/.match(v)
+ if mt[1].index('.')
+ v = mt[1].to_f
+ else
+ v = mt[1].to_i
+ end
+ cfg[k] = v * {
+ 'K' => 1000,
+ 'Ki' => 1 << 10,
+ 'M' => 1000000,
+ 'Mi' => 1 << 20,
+ "G" => 1000000000,
+ "Gi" => 1 << 30,
+ "T" => 1000000000000,
+ "Ti" => 1 << 40,
+ "P" => 1000000000000000,
+ "Pi" => 1 << 50,
+ "E" => 1000000000000000000,
+ "Ei" => 1 << 60,
+ }[mt[2]]
+ end
+ end
+
+ rescue => e
+ raise "#{cfgkey} expected #{cfgtype} but '#{cfg[k]}' got error #{e}"
+ end
+
+ if !cfg[k].is_a? cfgtype
+ raise "#{cfgkey} expected #{cfgtype} but was #{cfg[k].class}"
+ end
+ end
+ end
+
+ def self.set_cfg cfg, k, v
+ # "foo.bar = baz" --> { cfg["foo"]["bar"] = baz }
+ ks = k.split '.'
+ k = ks.pop
+ ks.each do |kk|
+ cfg = cfg[kk]
+ if cfg.nil?
+ break
+ end
+ end
+ if !cfg.nil?
+ cfg[k] = v
+ end
+ end
+
+ def self.parse_duration durstr, cfgkey:
+ duration_re = /-?(\d+(\.\d+)?)(s|m|h)/
+ dursec = 0
+ while durstr != ""
+ mt = duration_re.match durstr
+ if !mt
+ raise "#{cfgkey} not a valid duration: '#{durstr}', accepted suffixes are s, m, h"
+ end
+ multiplier = {s: 1, m: 60, h: 3600}
+ dursec += (Float(mt[1]) * multiplier[mt[3].to_sym])
+ durstr = durstr[mt[0].length..-1]
+ end
+ return dursec.seconds
+ end
+
+ def self.copy_into_config src, dst
+ src.each do |k, v|
+ dst.send "#{k}=", self.to_OrderedOptions(v)
+ end
+ end
+
+ def self.to_OrderedOptions confs
+ if confs.is_a? Hash
+ opts = ActiveSupport::OrderedOptions.new
+ confs.each do |k,v|
+ opts[k] = self.to_OrderedOptions(v)
+ end
+ opts
+ elsif confs.is_a? Array
+ confs.map { |v| self.to_OrderedOptions v }
+ else
+ confs
+ end
+ end
+
+ def self.load path, erb: false
+ if File.exist? path
+ yaml = IO.read path
+ if erb
+ yaml = ERB.new(yaml).result(binding)
+ end
+ YAML.load(yaml, deserialize_symbols: false)
+ else
+ {}
+ end
+ end
+
+end
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+def diff_hash base, final
+ diffed = {}
+ base.each do |k,v|
+ bk = base[k]
+ fk = final[k]
+ if bk.is_a? Hash
+ d = diff_hash bk, fk
+ if d.length > 0
+ diffed[k] = d
+ end
+ else
+ if bk.to_yaml != fk.to_yaml
+ diffed[k] = fk
+ end
+ end
+ end
+ diffed
+end
+
+namespace :config do
+ desc 'Print items that differ between legacy application.yml and system config.yml'
+ task diff: :environment do
+ diffed = diff_hash $arvados_config_global, $arvados_config
+ cfg = { "Clusters" => {}}
+ cfg["Clusters"][$arvados_config["ClusterID"]] = diffed.select {|k,v| k != "ClusterID"}
+ if cfg["Clusters"][$arvados_config["ClusterID"]].empty?
+ puts "No migrations required for /etc/arvados/config.yml"
+ else
+ puts cfg.to_yaml
+ end
+ end
+
+ desc 'Print config.yml after merging with legacy application.yml'
+ task migrate: :environment do
+ diffed = diff_hash $arvados_config_defaults, $arvados_config
+ cfg = { "Clusters" => {}}
+ cfg["Clusters"][$arvados_config["ClusterID"]] = diffed.select {|k,v| k != "ClusterID"}
+ puts cfg.to_yaml
+ end
+
+ desc 'Print configuration as accessed through Rails.configuration'
+ task dump: :environment do
+ combined = $arvados_config.deep_dup
+ combined.update $remaining_config
+ puts combined.to_yaml
+ end
+
+ desc 'Legacy config check task -- it is a noop now'
+ task check: :environment do
+ # This exists so that build/rails-package-scripts/postinst.sh doesn't fail.
+ end
+end
+++ /dev/null
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-namespace :config do
- desc 'Ensure site configuration has all required settings'
- task check: :environment do
- $application_config.sort.each do |k, v|
- if ENV.has_key?('QUIET') then
- # Make sure we still check for the variable to exist
- eval("Rails.configuration.#{k}")
- else
- if /(password|secret)/.match(k) then
- # Make sure we still check for the variable to exist, but don't print the value
- eval("Rails.configuration.#{k}")
- $stderr.puts "%-32s %s" % [k, '*********']
- else
- $stderr.puts "%-32s %s" % [k, eval("Rails.configuration.#{k}")]
- end
- end
- end
- end
-end
+++ /dev/null
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-namespace :config do
- desc 'Show site configuration'
- task dump: :environment do
- puts $application_config.to_yaml
- end
-end
ExternalURL: ""
WebShell:
InternalURLs: {}
+ # ShellInABox service endpoint URL for a given VM. If empty, do not
+ # offer web shell logins.
+ #
+ # E.g., using a path-based proxy server to forward connections to shell hosts:
+ # https://webshell.uuid_prefix.arvadosapi.com
+ #
+ # E.g., using a name-based proxy server to forward connections to shell hosts:
+ # https://*.webshell.uuid_prefix.arvadosapi.com
ExternalURL: ""
Workbench1:
InternalURLs: {}
NewUserNotificationRecipients: []
NewInactiveUserNotificationRecipients: []
+ # Set anonymous_user_token to enable anonymous user access. You can get
+ # the token by running "bundle exec ./script/get_anonymous_user_token.rb"
+ # in the directory where your API server is running.
+ AnonymousUserToken: ""
+
AuditLogs:
# Time to keep audit logs, in seconds. (An audit log is a row added
# to the "logs" table in the PostgreSQL database each time an
FormFieldDescription: ""
Required: true
UserProfileFormMessage: 'Welcome to Arvados. All <span style="color:red">required fields</span> must be completed before you can proceed.'
+
+ # Mimetypes of applications for which the view icon
+ # would be enabled in a collection's show page.
+ # It is sufficient to list only applications here.
+ # No need to list text and image types.
ApplicationMimetypesWithViewIcon:
cwl: {}
fasta: {}
PATH
remote: .
specs:
- arvados-login-sync (1.3.3.20190528194843)
+ arvados-login-sync (1.4.0.20190610174652)
arvados (~> 1.3.0, >= 1.3.0)
GEM
mocha (1.8.0)
metaclass (~> 0.0.1)
multi_json (1.13.1)
- multipart-post (2.1.1)
- os (1.0.1)
+ multipart-post (2.0.0)
+ os (1.0.0)
public_suffix (3.0.3)
rake (12.3.2)
retriable (1.4.1)