Merge branch '16678-login-tokens-lifetime-config'
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Tue, 25 Aug 2020 18:00:33 +0000 (15:00 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Tue, 25 Aug 2020 18:00:33 +0000 (15:00 -0300)
Closes #16678

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

63 files changed:
apps/workbench/Gemfile
apps/workbench/Gemfile.lock
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/models/application_record.rb [deleted file]
apps/workbench/app/models/arvados_base.rb
apps/workbench/bin/bundle
apps/workbench/bin/setup
apps/workbench/bin/update
apps/workbench/bin/yarn [new file with mode: 0755]
apps/workbench/config/application.default.yml
apps/workbench/config/application.rb
apps/workbench/config/boot.rb
apps/workbench/config/initializers/content_security_policy.rb [new file with mode: 0644]
apps/workbench/config/initializers/new_framework_defaults.rb
apps/workbench/config/initializers/new_framework_defaults_5_1.rb [new file with mode: 0644]
apps/workbench/config/initializers/new_framework_defaults_5_2.rb [new file with mode: 0644]
apps/workbench/config/routes.rb
apps/workbench/config/secrets.yml
build/run-build-docker-jobs-image.sh
build/run-tests.sh
doc/user/topics/arv-web.html.textile.liquid [deleted file]
lib/boot/supervisor.go
lib/cloud/azure/azure.go
lib/config/config.default.yml
lib/config/export.go
lib/config/generated_config.go
lib/controller/federation/federation_test.go
lib/controller/localdb/login.go
lib/controller/localdb/login_ldap_test.go
lib/controller/localdb/login_testuser.go [new file with mode: 0644]
lib/controller/localdb/login_testuser_test.go [new file with mode: 0644]
lib/dispatchcloud/dispatcher_test.go
lib/dispatchcloud/scheduler/run_queue.go
lib/dispatchcloud/scheduler/run_queue_test.go
lib/dispatchcloud/scheduler/sync.go
lib/dispatchcloud/test/queue.go
lib/dispatchcloud/test/stub_driver.go
sdk/go/arvados/config.go
sdk/go/arvados/fs_collection.go
sdk/go/arvados/fs_project_test.go
sdk/go/keepclient/keepclient_test.go
services/api/config/application.rb
services/arv-git-httpd/gitolite_test.go
services/arv-web/README [deleted file]
services/arv-web/arv-web.py [deleted file]
services/arv-web/sample-cgi-app/docker_image [deleted file]
services/arv-web/sample-cgi-app/public/.htaccess [deleted file]
services/arv-web/sample-cgi-app/public/index.cgi [deleted file]
services/arv-web/sample-cgi-app/tmp/.keepkeep [deleted file]
services/arv-web/sample-rack-app/config.ru [deleted file]
services/arv-web/sample-rack-app/docker_image [deleted file]
services/arv-web/sample-rack-app/public/.keepkeep [deleted file]
services/arv-web/sample-rack-app/tmp/.keepkeep [deleted file]
services/arv-web/sample-static-page/docker_image [deleted file]
services/arv-web/sample-static-page/public/index.html [deleted file]
services/arv-web/sample-static-page/tmp/.keepkeep [deleted file]
services/arv-web/sample-wsgi-app/docker_image [deleted file]
services/arv-web/sample-wsgi-app/passenger_wsgi.py [deleted file]
services/arv-web/sample-wsgi-app/public/.keepkeep [deleted file]
services/arv-web/sample-wsgi-app/tmp/.keepkeep [deleted file]
services/keep-web/handler.go
services/keep-web/s3.go
services/keep-web/s3_test.go

index 24bfba383fc7065a5709293acb91943258a2842a..d5b416b5396f678b029bbaa20fb6bdba1e8a6bb2 100644 (file)
@@ -4,7 +4,7 @@
 
 source 'https://rubygems.org'
 
-gem 'rails', '~> 5.0.0'
+gem 'rails', '~> 5.2.0'
 gem 'arvados', git: 'https://github.com/arvados/arvados.git', glob: 'sdk/ruby/arvados.gemspec'
 
 gem 'activerecord-nulldb-adapter', git: 'https://github.com/arvados/nulldb'
@@ -14,6 +14,13 @@ gem 'sass'
 gem 'mime-types'
 gem 'responders', '~> 2.0'
 
+# Pin sprockets to < 4.0 to avoid issues when upgrading rails to 5.2
+# See: https://github.com/rails/sprockets-rails/issues/443
+gem 'sprockets', '~> 3.0'
+
+# Fast app boot times
+gem 'bootsnap', require: false
+
 # Note: keeping this out of the "group :assets" section "may" allow us
 # to use Coffescript for UJS responses. It also prevents a
 # warning/problem when running tests: "WARN: tilt autoloading
@@ -31,8 +38,14 @@ group :assets do
   gem 'therubyracer', :platforms => :ruby
 end
 
-group :development do
+group :development, :test, :performance do
   gem 'byebug'
+  # Pinning launchy because 2.5 requires ruby >= 2.4, which arvbox currently
+  # doesn't have because of SSO.
+  gem 'launchy', '~> 2.4.0'
+end
+
+group :development do
   gem 'ruby-debug-passenger'
   gem 'rack-mini-profiler', require: false
   gem 'flamegraph', require: false
@@ -48,7 +61,6 @@ group :test, :diagnostics, :performance do
 end
 
 group :test, :performance do
-  gem 'byebug'
   gem 'rails-perftest'
   gem 'ruby-prof'
   gem 'rvm-capistrano'
@@ -70,12 +82,6 @@ gem 'angularjs-rails', '~> 1.3.8'
 
 gem 'less'
 gem 'less-rails'
-
-# Wiselinks hasn't been updated for many years and it's using deprecated methods
-# Use our own Wiselinks fork until this PR is accepted:
-# https://github.com/igor-alexandrov/wiselinks/pull/116
-# gem 'wiselinks', git: 'https://github.com/arvados/wiselinks.git', branch: 'rails-5.1-compatibility'
-
 gem 'sshkey'
 
 # To use ActiveModel has_secure_password
index cb4e7ab9e334cb8fdb0ae72c20ee841f4fed02b2..e19172cb2ee54bba81f29e7f803f7b21f7b27f50 100644 (file)
@@ -30,39 +30,43 @@ GEM
   remote: https://rubygems.org/
   specs:
     RedCloth (4.3.2)
-    actioncable (5.0.7.2)
-      actionpack (= 5.0.7.2)
-      nio4r (>= 1.2, < 3.0)
-      websocket-driver (~> 0.6.1)
-    actionmailer (5.0.7.2)
-      actionpack (= 5.0.7.2)
-      actionview (= 5.0.7.2)
-      activejob (= 5.0.7.2)
+    actioncable (5.2.4.3)
+      actionpack (= 5.2.4.3)
+      nio4r (~> 2.0)
+      websocket-driver (>= 0.6.1)
+    actionmailer (5.2.4.3)
+      actionpack (= 5.2.4.3)
+      actionview (= 5.2.4.3)
+      activejob (= 5.2.4.3)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 2.0)
-    actionpack (5.0.7.2)
-      actionview (= 5.0.7.2)
-      activesupport (= 5.0.7.2)
-      rack (~> 2.0)
-      rack-test (~> 0.6.3)
+    actionpack (5.2.4.3)
+      actionview (= 5.2.4.3)
+      activesupport (= 5.2.4.3)
+      rack (~> 2.0, >= 2.0.8)
+      rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.0.2)
-    actionview (5.0.7.2)
-      activesupport (= 5.0.7.2)
+    actionview (5.2.4.3)
+      activesupport (= 5.2.4.3)
       builder (~> 3.1)
-      erubis (~> 2.7.0)
+      erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.0.3)
-    activejob (5.0.7.2)
-      activesupport (= 5.0.7.2)
+    activejob (5.2.4.3)
+      activesupport (= 5.2.4.3)
       globalid (>= 0.3.6)
-    activemodel (5.0.7.2)
-      activesupport (= 5.0.7.2)
-    activerecord (5.0.7.2)
-      activemodel (= 5.0.7.2)
-      activesupport (= 5.0.7.2)
-      arel (~> 7.0)
-    activesupport (5.0.7.2)
+    activemodel (5.2.4.3)
+      activesupport (= 5.2.4.3)
+    activerecord (5.2.4.3)
+      activemodel (= 5.2.4.3)
+      activesupport (= 5.2.4.3)
+      arel (>= 9.0)
+    activestorage (5.2.4.3)
+      actionpack (= 5.2.4.3)
+      activerecord (= 5.2.4.3)
+      marcel (~> 0.3.1)
+    activesupport (5.2.4.3)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
@@ -71,9 +75,9 @@ GEM
       public_suffix (>= 2.0.2, < 5.0)
     andand (1.3.3)
     angularjs-rails (1.3.15)
-    arel (7.1.4)
-    arvados-google-api-client (0.8.7.3)
-      activesupport (>= 3.2, < 5.1)
+    arel (9.0.0)
+    arvados-google-api-client (0.8.7.4)
+      activesupport (>= 3.2, < 5.3)
       addressable (~> 2.3)
       autoparse (~> 0.3)
       extlib (~> 0.9)
@@ -89,6 +93,8 @@ GEM
       multi_json (>= 1.0.0)
     autoprefixer-rails (9.5.1.1)
       execjs
+    bootsnap (1.4.7)
+      msgpack (~> 1.0)
     bootstrap-sass (3.4.1)
       autoprefixer-rails (>= 5.2.1)
       sassc (>= 2.0.0)
@@ -96,7 +102,7 @@ GEM
       railties (>= 3.1)
     bootstrap-x-editable-rails (1.5.1.1)
       railties (>= 3.0)
-    builder (3.2.3)
+    builder (3.2.4)
     byebug (11.0.1)
     capistrano (2.15.9)
       highline
@@ -121,11 +127,11 @@ GEM
       execjs
     coffee-script-source (1.12.2)
     commonjs (0.2.7)
-    concurrent-ruby (1.1.5)
-    crass (1.0.5)
+    concurrent-ruby (1.1.6)
+    crass (1.0.6)
     deep_merge (1.2.1)
     docile (1.3.1)
-    erubis (2.7.0)
+    erubi (1.9.0)
     execjs (2.7.0)
     extlib (0.9.16)
     faraday (0.15.4)
@@ -167,25 +173,29 @@ GEM
       railties (>= 4)
       request_store (~> 1.0)
     logstash-event (1.2.02)
-    loofah (2.3.1)
+    loofah (2.6.0)
       crass (~> 1.0.2)
       nokogiri (>= 1.5.9)
     mail (2.7.1)
       mini_mime (>= 0.1.1)
+    marcel (0.3.3)
+      mimemagic (~> 0.3.2)
     memoist (0.16.2)
     metaclass (0.0.4)
-    method_source (0.9.2)
+    method_source (1.0.0)
     mime-types (3.2.2)
       mime-types-data (~> 3.2015)
     mime-types-data (3.2019.0331)
-    mini_mime (1.0.1)
+    mimemagic (0.3.5)
+    mini_mime (1.0.2)
     mini_portile2 (2.4.0)
     minitest (5.10.3)
     mocha (1.8.0)
       metaclass (~> 0.0.1)
     morrisjs-rails (0.5.1.2)
       railties (> 3.1, < 6)
-    multi_json (1.14.1)
+    msgpack (1.3.3)
+    multi_json (1.15.0)
     multipart-post (2.1.1)
     net-scp (2.0.0)
       net-ssh (>= 2.6.5, < 6.0.0)
@@ -194,13 +204,13 @@ GEM
     net-ssh (5.2.0)
     net-ssh-gateway (2.0.0)
       net-ssh (>= 4.0.0)
-    nio4r (2.3.1)
-    nokogiri (1.10.8)
+    nio4r (2.5.2)
+    nokogiri (1.10.10)
       mini_portile2 (~> 2.4.0)
     npm-rails (0.2.1)
       rails (>= 3.2)
     oj (3.7.12)
-    os (1.0.1)
+    os (1.1.1)
     passenger (6.0.2)
       rack
       rake (>= 0.8.1)
@@ -213,23 +223,24 @@ GEM
       cliver (~> 0.3.1)
       multi_json (~> 1.0)
       websocket-driver (>= 0.2.0)
-    public_suffix (4.0.3)
+    public_suffix (4.0.5)
     rack (2.2.3)
     rack-mini-profiler (1.0.2)
       rack (>= 1.2.0)
-    rack-test (0.6.3)
-      rack (>= 1.0)
-    rails (5.0.7.2)
-      actioncable (= 5.0.7.2)
-      actionmailer (= 5.0.7.2)
-      actionpack (= 5.0.7.2)
-      actionview (= 5.0.7.2)
-      activejob (= 5.0.7.2)
-      activemodel (= 5.0.7.2)
-      activerecord (= 5.0.7.2)
-      activesupport (= 5.0.7.2)
+    rack-test (1.1.0)
+      rack (>= 1.0, < 3)
+    rails (5.2.4.3)
+      actioncable (= 5.2.4.3)
+      actionmailer (= 5.2.4.3)
+      actionpack (= 5.2.4.3)
+      actionview (= 5.2.4.3)
+      activejob (= 5.2.4.3)
+      activemodel (= 5.2.4.3)
+      activerecord (= 5.2.4.3)
+      activestorage (= 5.2.4.3)
+      activesupport (= 5.2.4.3)
       bundler (>= 1.3.0)
-      railties (= 5.0.7.2)
+      railties (= 5.2.4.3)
       sprockets-rails (>= 2.0.0)
     rails-controller-testing (1.0.4)
       actionpack (>= 5.0.1.x)
@@ -238,15 +249,15 @@ GEM
     rails-dom-testing (2.0.3)
       activesupport (>= 4.2.0)
       nokogiri (>= 1.6)
-    rails-html-sanitizer (1.0.4)
-      loofah (~> 2.2, >= 2.2.2)
+    rails-html-sanitizer (1.3.0)
+      loofah (~> 2.3)
     rails-perftest (0.0.7)
-    railties (5.0.7.2)
-      actionpack (= 5.0.7.2)
-      activesupport (= 5.0.7.2)
+    railties (5.2.4.3)
+      actionpack (= 5.2.4.3)
+      activesupport (= 5.2.4.3)
       method_source
       rake (>= 0.8.7)
-      thor (>= 0.18.1, < 2.0)
+      thor (>= 0.19.0, < 2.0)
     rake (13.0.1)
     raphael-rails (2.1.2)
     rb-fsevent (0.10.3)
@@ -305,15 +316,15 @@ GEM
     therubyracer (0.12.3)
       libv8 (~> 3.16.14.15)
       ref
-    thor (0.20.3)
+    thor (1.0.1)
     thread_safe (0.3.6)
     tilt (2.0.9)
-    tzinfo (1.2.6)
+    tzinfo (1.2.7)
       thread_safe (~> 0.1)
     uglifier (2.7.2)
       execjs (>= 0.3.0)
       json (>= 1.8.0)
-    websocket-driver (0.6.5)
+    websocket-driver (0.7.3)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.5)
     xpath (2.1.0)
@@ -328,6 +339,7 @@ DEPENDENCIES
   andand
   angularjs-rails (~> 1.3.8)
   arvados!
+  bootsnap
   bootstrap-sass (~> 3.4.1)
   bootstrap-tab-history-rails
   bootstrap-x-editable-rails
@@ -339,6 +351,7 @@ DEPENDENCIES
   headless (~> 1.0.2)
   httpclient (~> 2.5)
   jquery-rails
+  launchy (~> 2.4.0)
   less
   less-rails
   lograge
@@ -354,7 +367,7 @@ DEPENDENCIES
   piwik_analytics
   poltergeist (~> 1.5.1)
   rack-mini-profiler
-  rails (~> 5.0.0)
+  rails (~> 5.2.0)
   rails-controller-testing
   rails-perftest
   raphael-rails
@@ -369,10 +382,11 @@ DEPENDENCIES
   signet (< 0.12)
   simplecov (~> 0.7)
   simplecov-rcov
+  sprockets (~> 3.0)
   sshkey
   themes_for_rails!
   therubyracer
   uglifier (~> 2.0)
 
 BUNDLED WITH
-   1.16.6
+   1.17.3
index 8d6f897bb69b1770054d15337cb28cb6bf507876..77ec68bdb06eeb5d9bf124206528e62c491c547e 100644 (file)
@@ -29,7 +29,6 @@ class ApplicationController < ActionController::Base
   begin
     rescue_from(ActiveRecord::RecordNotFound,
                 ActionController::RoutingError,
-                ActionController::UnknownController,
                 AbstractController::ActionNotFound,
                 with: :render_not_found)
     rescue_from(Exception,
diff --git a/apps/workbench/app/models/application_record.rb b/apps/workbench/app/models/application_record.rb
deleted file mode 100644 (file)
index 759034d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class ApplicationRecord < ActiveRecord::Base
-  self.abstract_class = true
-end
\ No newline at end of file
index b9162c2aec364bd03a34171c9981262304bc9d06..c5e1a4ed2240075691fd6e03827c746be950f3aa 100644 (file)
@@ -106,6 +106,12 @@ class ArvadosBase
     end
   end
 
+  # The ActiveModel::Dirty API was changed on Rails 5.2
+  # See: https://github.com/rails/rails/commit/c3675f50d2e59b7fc173d7b332860c4b1a24a726#diff-aaddd42c7feb0834b1b5c66af69814d3
+  def mutations_from_database
+    @mutations_from_database ||= ActiveModel::NullMutationTracker.instance
+  end
+
   def self.columns
     @discovered_columns = [] if !defined?(@discovered_columns)
     return @discovered_columns if @discovered_columns.andand.any?
index 9447ba861219ee65467e1df44115bd4ceb87bf63..cb10307acd6cb385375417f47172eff01990dde4 100755 (executable)
@@ -3,5 +3,5 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
 load Gem.bin_path('bundler', 'bundle')
index 50c3fa0548ccb97521e6ca22885209f81257ea24..7aed0fb2826fc94553b99f87db10cf379daad69d 100755 (executable)
@@ -3,12 +3,11 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-require 'pathname'
 require 'fileutils'
 include FileUtils
 
 # path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = File.expand_path('..', __dir__)
 
 def system!(*args)
   system(*args) || abort("\n== Command #{args} failed ==")
@@ -22,6 +21,9 @@ chdir APP_ROOT do
   system! 'gem install bundler --conservative'
   system('bundle check') || system!('bundle install')
 
+  # Install JavaScript dependencies if using Yarn
+  # system('bin/yarn')
+
   # puts "\n== Copying sample files =="
   # unless File.exist?('config/database.yml')
   #   cp 'config/database.yml.sample', 'config/database.yml'
index b56771ece80ef11fc0e444889ecfe3d4fb23517a..46aa76ca87a921a313af9d3756a13f56629d92ab 100755 (executable)
@@ -22,6 +22,9 @@ chdir APP_ROOT do
   system! 'gem install bundler --conservative'
   system('bundle check') || system!('bundle install')
 
+  # Install JavaScript dependencies if using Yarn
+  # system('bin/yarn')
+
   puts "\n== Updating database =="
   system! 'bin/rails db:migrate'
 
diff --git a/apps/workbench/bin/yarn b/apps/workbench/bin/yarn
new file mode 100755 (executable)
index 0000000..5fc7611
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/env ruby
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+APP_ROOT = File.expand_path('..', __dir__)
+Dir.chdir(APP_ROOT) do
+  begin
+    exec "yarnpkg #{ARGV.join(" ")}"
+  rescue Errno::ENOENT
+    $stderr.puts "Yarn executable was not detected in the system."
+    $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
+    exit 1
+  end
+end
index 9456e61455c306cb7b19db7963366c34a55b1345..255ad44f852f4b567005eae0c81a8f445dfbea8b 100644 (file)
@@ -77,7 +77,6 @@ test:
   action_mailer.delivery_method: :test
   active_support.deprecation: :stderr
   profiling_enabled: true
-  secret_token: <%= rand(2**256).to_s(36) %>
   secret_key_base: <%= rand(2**256).to_s(36) %>
   site_name: Workbench:test
 
index e88229b85158f200ebc6a7df644f9b147fcfd06f..42bf4da24bbf71900d403686cc954badd57660e0 100644 (file)
@@ -2,13 +2,15 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-require File.expand_path('../boot', __FILE__)
+require_relative 'boot'
 
 require "rails"
 # Pick only the frameworks we need:
 require "active_model/railtie"
 require "active_job/railtie"
 require "active_record/railtie"
+# Skip ActiveStorage (new in Rails 5.1)
+# require "active_storage/engine"
 require "action_controller/railtie"
 require "action_mailer/railtie"
 require "action_view/railtie"
@@ -28,6 +30,9 @@ module ArvadosWorkbench
 
     require_relative "arvados_config.rb"
 
+    # Initialize configuration defaults for originally generated Rails version.
+    config.load_defaults 5.1
+
     # 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.
index 8153266683f6161a8666f74843ce6810d093ffc0..6add5911f6238f87ff72b91fef710fc05d9b67ba 100644 (file)
@@ -8,6 +8,7 @@ require 'rubygems'
 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
 
 require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
+require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
 
 # Use ARVADOS_API_TOKEN environment variable (if set) in console
 require 'rails'
diff --git a/apps/workbench/config/initializers/content_security_policy.rb b/apps/workbench/config/initializers/content_security_policy.rb
new file mode 100644 (file)
index 0000000..853ecde
--- /dev/null
@@ -0,0 +1,29 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+# Be sure to restart your server when you modify this file.
+
+# Define an application-wide content security policy
+# For further information see the following documentation
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+
+# Rails.application.config.content_security_policy do |policy|
+#   policy.default_src :self, :https
+#   policy.font_src    :self, :https, :data
+#   policy.img_src     :self, :https, :data
+#   policy.object_src  :none
+#   policy.script_src  :self, :https
+#   policy.style_src   :self, :https
+
+#   # Specify URI for violation reports
+#   # policy.report_uri "/csp-violation-report-endpoint"
+# end
+
+# If you are using UJS then enable automatic nonce generation
+# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
+
+# Report CSP violations to a specified URI
+# For further information see the following documentation:
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
+# Rails.application.config.content_security_policy_report_only = true
index b8dca33a373171cceda0b9d9f3f2d5f6f1dc68e9..2e2f0b1810df54f5a5a4b15e6a07c5be98e78f67 100644 (file)
@@ -24,6 +24,3 @@ ActiveSupport.to_time_preserves_timezone = false
 
 # Require `belongs_to` associations by default. Previous versions had false.
 Rails.application.config.active_record.belongs_to_required_by_default = false
-
-# Do not halt callback chains when a callback returns false. Previous versions had true.
-ActiveSupport.halt_callback_chains_on_return_false = true
diff --git a/apps/workbench/config/initializers/new_framework_defaults_5_1.rb b/apps/workbench/config/initializers/new_framework_defaults_5_1.rb
new file mode 100644 (file)
index 0000000..804ee6f
--- /dev/null
@@ -0,0 +1,18 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.1 upgrade.
+#
+# Once upgraded flip defaults one by one to migrate to the new default.
+#
+# Read the Guide for Upgrading Ruby on Rails for more info on each option.
+
+# Make `form_with` generate non-remote forms.
+Rails.application.config.action_view.form_with_generates_remote_forms = false
+
+# Unknown asset fallback will return the path passed in when the given
+# asset is not present in the asset pipeline.
+# Rails.application.config.assets.unknown_asset_fallback = false
diff --git a/apps/workbench/config/initializers/new_framework_defaults_5_2.rb b/apps/workbench/config/initializers/new_framework_defaults_5_2.rb
new file mode 100644 (file)
index 0000000..93a8d52
--- /dev/null
@@ -0,0 +1,42 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.2 upgrade.
+#
+# Once upgraded flip defaults one by one to migrate to the new default.
+#
+# Read the Guide for Upgrading Ruby on Rails for more info on each option.
+
+# Make Active Record use stable #cache_key alongside new #cache_version method.
+# This is needed for recyclable cache keys.
+# Rails.application.config.active_record.cache_versioning = true
+
+# Use AES-256-GCM authenticated encryption for encrypted cookies.
+# Also, embed cookie expiry in signed or encrypted cookies for increased security.
+#
+# This option is not backwards compatible with earlier Rails versions.
+# It's best enabled when your entire app is migrated and stable on 5.2.
+#
+# Existing cookies will be converted on read then written with the new scheme.
+# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
+
+# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages
+# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true.
+# Rails.application.config.active_support.use_authenticated_message_encryption = true
+
+# Add default protection from forgery to ActionController::Base instead of in
+# ApplicationController.
+# Rails.application.config.action_controller.default_protect_from_forgery = true
+
+# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
+# 'f' after migrating old data.
+# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
+
+# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header.
+# Rails.application.config.active_support.use_sha1_digests = true
+
+# Make `form_with` generate id attributes for any generated HTML tags.
+# Rails.application.config.action_view.form_with_generates_ids = true
index 718adfd2ed0583a99f8eebb221b5eae0c7d012c3..ffc09ac933acf8d88fa1b07cac460c144a805a45 100644 (file)
@@ -2,7 +2,7 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-ArvadosWorkbench::Application.routes.draw do
+Rails.application.routes.draw do
   themes_for_rails
 
   resources :keep_disks
index bc8a0d0de5f6852eff8e2c0092909b4f16660ae4..57399082e8a342cfcfaecf2637bafdb9727cd136 100644 (file)
 # no regular words or you'll be exposed to dictionary attacks.
 # You can use `rails secret` to generate a secure secret key.
 
-# Make sure the secrets in this file are kept private
-# if you're sharing your code publicly.
+# NOTE that these get overriden by Arvados' own configuration system.
 
-development:
-  secret_key_base: 33e2d171ec6c67cf8e9a9fbfadc1071328bdab761297e2fe28b9db7613dd542c1ba3bdb3bd3e636d1d6f74ab73a2d90c4e9c0ecc14fde8ccd153045f94e9cc41
+development:
+#   secret_key_base: <%= rand(1<<255).to_s(36) %>
 
-test:
-  secret_key_base: d4c07cab3530fccf5d86565ecdc359eb2a853b8ede3b06edb2885e4423d7a726f50a3e415bb940fd4861e8fec16459665fd377acc8cdd98ea63294d2e0d12bb2
+test:
+#   secret_key_base: <%= rand(1<<255).to_s(36) %>
 
-# Do not keep production secrets in the repository,
-# instead read values from the environment.
+# In case this doesn't get overriden for some reason, assign a random key
+# to gracefully degrade by rejecting cookies instead of by opening a
+# vulnerability.
 production:
-  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+  secret_key_base: <%= rand(1<<255).to_s(36) %>
index ec8357701d067fe0b17bdc2df01f17a1bf4f948e..d1fb2ac67054dfdc31ce8a31401747c3a55aefbf 100755 (executable)
@@ -185,28 +185,23 @@ if docker --version |grep " 1\.[0-9]\." ; then
     FORCE=-f
 fi
 
-#docker export arvados/jobs:$cwl_runner_version_orig | docker import - arvados/jobs:$cwl_runner_version_orig
-
 if ! [[ -z "$version_tag" ]]; then
     docker tag $FORCE arvados/jobs:$cwl_runner_version_orig arvados/jobs:"$version_tag"
-else
-    docker tag $FORCE arvados/jobs:$cwl_runner_version_orig arvados/jobs:latest
-fi
+    ECODE=$?
 
-ECODE=$?
+    if [[ "$ECODE" != "0" ]]; then
+        EXITCODE=$(($EXITCODE + $ECODE))
+    fi
 
-if [[ "$ECODE" != "0" ]]; then
-    EXITCODE=$(($EXITCODE + $ECODE))
+    checkexit $ECODE "docker tag"
+    title "docker tag complete (`timer`)"
 fi
 
-checkexit $ECODE "docker tag"
-title "docker tag complete (`timer`)"
-
 title "uploading images"
 
 timer_reset
 
-if [[ "$ECODE" != "0" ]]; then
+if [[ "$EXITCODE" != "0" ]]; then
     title "upload arvados images SKIPPED because build or tag failed"
 else
     if [[ $upload == true ]]; then
@@ -217,7 +212,6 @@ else
             docker_push arvados/jobs:"$version_tag"
         else
            docker_push arvados/jobs:$cwl_runner_version_orig
-           docker_push arvados/jobs:latest
         fi
         title "upload arvados images finished (`timer`)"
     else
index 2742540b16b44efe57fa113d23d3e967915e5c2f..bedc95b2db28503d2505bc9ce840a06a89662496 100755 (executable)
@@ -195,7 +195,7 @@ sanity_checks() {
     ( [[ -n "$WORKSPACE" ]] && [[ -d "$WORKSPACE/services" ]] ) \
         || fatal "WORKSPACE environment variable not set to a source directory (see: $0 --help)"
     [[ -z "$CONFIGSRC" ]] || [[ -s "$CONFIGSRC/config.yml" ]] \
-       || fatal "CONFIGSRC is $CONFIGSRC but '$CONFIGSRC/config.yml' is empty or not found (see: $0 --help)"
+        || fatal "CONFIGSRC is $CONFIGSRC but '$CONFIGSRC/config.yml' is empty or not found (see: $0 --help)"
     echo Checking dependencies:
     echo "locale: ${LANG}"
     [[ "$(locale charmap)" = "UTF-8" ]] \
@@ -373,7 +373,7 @@ if [[ ${skip["sdk/R"]} == 1 && ${skip["doc"]} == 1 ]]; then
 fi
 
 if [[ $NEED_SDK_R == false ]]; then
-       echo "R SDK not needed, it will not be installed."
+        echo "R SDK not needed, it will not be installed."
 fi
 
 checkpidfile() {
@@ -414,11 +414,11 @@ start_services() {
     . "$VENVDIR/bin/activate"
     echo 'Starting API, controller, keepproxy, keep-web, arv-git-httpd, ws, and nginx ssl proxy...'
     if [[ ! -d "$WORKSPACE/services/api/log" ]]; then
-       mkdir -p "$WORKSPACE/services/api/log"
+        mkdir -p "$WORKSPACE/services/api/log"
     fi
     # Remove empty api.pid file if it exists
     if [[ -f "$WORKSPACE/tmp/api.pid" && ! -s "$WORKSPACE/tmp/api.pid" ]]; then
-       rm -f "$WORKSPACE/tmp/api.pid"
+        rm -f "$WORKSPACE/tmp/api.pid"
     fi
     all_services_stopped=
     fail=1
@@ -817,19 +817,19 @@ do_test_once() {
 
 check_arvados_config() {
     if [[ "$1" = "env" ]] ; then
-       return
+        return
     fi
     if [[ -z "$ARVADOS_CONFIG" ]] ; then
-       # Create config file.  The run_test_server script requires PyYAML,
-       # so virtualenv needs to be active.  Downstream steps like
-       # workbench install which require a valid config.yml.
-       if [[ ! -s "$VENVDIR/bin/activate" ]] ; then
-           install_env
-       fi
-       . "$VENVDIR/bin/activate"
+        # Create config file.  The run_test_server script requires PyYAML,
+        # so virtualenv needs to be active.  Downstream steps like
+        # workbench install which require a valid config.yml.
+        if [[ ! -s "$VENVDIR/bin/activate" ]] ; then
+            install_env
+        fi
+        . "$VENVDIR/bin/activate"
         cd "$WORKSPACE"
-       eval $(python sdk/python/tests/run_test_server.py setup_config)
-       deactivate
+        eval $(python sdk/python/tests/run_test_server.py setup_config)
+        deactivate
     fi
 }
 
diff --git a/doc/user/topics/arv-web.html.textile.liquid b/doc/user/topics/arv-web.html.textile.liquid
deleted file mode 100644 (file)
index 9671e97..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
----
-layout: default
-navsection: userguide
-title: "Using arv-web"
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-@arv-web@ enables you to run a custom web service from the contents of an Arvados collection.
-
-{% include 'tutorial_expectations_workstation' %}
-
-h2. Usage
-
-@arv-web@ enables you to set up a web service based on the most recent collection in a project.  An arv-web application is a reproducible, immutable application bundle where the web app is packaged with both the code to run and the data to serve.  Because Arvados Collections can be updated with minimum duplication, it is efficient to produce a new application bundle when the code or data needs to be updated; retaining old application bundles makes it easy to go back and run older versions of your web app.
-
-<pre>
-$ cd $HOME/arvados/services/arv-web
-usage: arv-web.py [-h] --project-uuid PROJECT_UUID [--port PORT]
-                  [--image IMAGE]
-
-optional arguments:
-  -h, --help            show this help message and exit
-  --project-uuid PROJECT_UUID
-                        Project uuid to watch
-  --port PORT           Host port to listen on (default 8080)
-  --image IMAGE         Docker image to run
-</pre>
-
-At startup, @arv-web@ queries an Arvados project and mounts the most recently modified collection into a temporary directory.  It then runs a Docker image with the collection bound to @/mnt@ inside the container.  When a new collection is added to the project, or an existing project is updated, it will stop the running Docker container, unmount the old collection, mount the new most recently modified collection, and restart the Docker container with the new mount.
-
-h2. Docker container
-
-The @Dockerfile@ in @arvados/docker/arv-web@ builds a Docker image that runs Apache with @/mnt@ as the DocumentRoot.  It is configured to run web applications which use Python WSGI, Ruby Rack, or CGI; to serve static HTML; or browse the contents of the @public@ subdirectory of the collection using default Apache index pages.
-
-To build the Docker image:
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd arvados/docker</span>
-~/arvados/docker$ <span class="userinput">docker build -t arvados/arv-web arv-web</span>
-</code></pre>
-</notextile>
-
-h2. Running sample applications
-
-First, in Arvados Workbench, create a new project.  Copy the project UUID from the URL bar (this is the part of the URL after @projects/...@).
-
-Now upload a collection containing a "Python WSGI web app:":http://wsgi.readthedocs.org/en/latest/
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd arvados/services/arv-web</span>
-~/arvados/services/arv-web$ <span class="userinput">arv-put --project [zzzzz-j7d0g-yourprojectuuid] --name sample-wsgi-app sample-wsgi-app</span>
-0M / 0M 100.0%
-Collection saved as 'sample-wsgi-app'
-zzzzz-4zz18-ebohzfbzh82qmqy
-~/arvados/services/arv-web$ <span class="userinput">./arv-web.py --project [zzzzz-j7d0g-yourprojectuuid] --port 8888</span>
-2015-01-30 11:21:00 arvados.arv-web[4897] INFO: Mounting zzzzz-4zz18-ebohzfbzh82qmqy
-2015-01-30 11:21:01 arvados.arv-web[4897] INFO: Starting Docker container arvados/arv-web
-2015-01-30 11:21:02 arvados.arv-web[4897] INFO: Container id e79e70558d585a3e038e4bfbc97e5c511f21b6101443b29a8017bdf3d84689a3
-2015-01-30 11:21:03 arvados.arv-web[4897] INFO: Waiting for events
-</code></pre>
-</notextile>
-
-The sample application will be available at @http://localhost:8888@.
-
-h3. Updating the application
-
-If you upload a new collection to the same project, arv-web will restart the web service and serve the new collection.  For example, uploading a collection containing a "Ruby Rack web app:":https://github.com/rack/rack/wiki
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd arvados/services/arv-web</span>
-~/arvados/services/arv-web$ <span class="userinput">arv-put --project [zzzzz-j7d0g-yourprojectuuid] --name sample-rack-app sample-rack-app</span>
-0M / 0M 100.0%
-Collection saved as 'sample-rack-app'
-zzzzz-4zz18-dhhm0ay8k8cqkvg
-</code></pre>
-</notextile>
-
-@arv-web@ will automatically notice the change, load a new container, and send an update signal (SIGHUP) to the service:
-
-<pre>
-2015-01-30 11:21:03 arvados.arv-web[4897] INFO:Waiting for events
-2015-01-30 11:21:04 arvados.arv-web[4897] INFO:create zzzzz-4zz18-dhhm0ay8k8cqkvg
-2015-01-30 11:21:05 arvados.arv-web[4897] INFO:Mounting zzzzz-4zz18-dhhm0ay8k8cqkvg
-2015-01-30 11:21:06 arvados.arv-web[4897] INFO:Sending refresh signal to container
-2015-01-30 11:21:07 arvados.arv-web[4897] INFO:Waiting for events
-</pre>
-
-h2. Writing your own applications
-
-The @arvados/arv-web@ image serves Python and Ruby applications using Phusion Passenger and Apache @mod_passenger@.  See "Phusion Passenger users guide for Apache":https://www.phusionpassenger.com/documentation/Users%20guide%20Apache.html for details, and look at the sample apps @arvados/services/arv-web/sample-wsgi-app@ and @arvados/services/arv-web/sample-rack-app@.
-
-You can serve CGI applications using standard Apache CGI support.  See "Apache Tutorial: Dynamic Content with CGI":https://httpd.apache.org/docs/current/howto/cgi.html for details, and look at the sample app @arvados/services/arv-web/sample-cgi-app@.
-
-You can also serve static content from the @public@ directory of the collection.  Look at @arvados/services/arv-web/sample-static-page@ for an example.  If no @index.html@ is found in @public/@, it will render default Apache index pages, permitting simple browsing of the collection contents.
-
-h3. Custom images
-
-You can provide your own Docker image.  The Docker image that will be used create the web application container is specified in the @docker_image@ file in the root of the collection.  You can also specify @--image@ on the command @arv-web@ line to choose the docker image (this will override the contents of @docker_image@).
-
-h3. Reloading the web service
-
-Stopping the Docker container and starting it again can result in a small amount of downtime.  When the collection containing a new or updated web application uses the same Docker image as the currently running web application, it is possible to avoid this downtime by keeping the existing container and only reloading the web server.  This is accomplished by providing a file called @reload@ in the root of the collection, which should contain the commands necessary to reload the web server inside the container.
index e38a4775e87f799b3641ac9b50f524b6b9e2df99..3f4fb7482229bc704e9daee1d71a0775aa8ed3fa 100644 (file)
@@ -601,7 +601,7 @@ func (super *Supervisor) autofillConfig(cfg *arvados.Config) error {
                }
                if len(svc.InternalURLs) == 0 {
                        svc.InternalURLs = map[arvados.URL]arvados.ServiceInstance{
-                               arvados.URL{Scheme: "http", Host: fmt.Sprintf("%s:%s", super.ListenHost, nextPort(super.ListenHost)), Path: "/"}: arvados.ServiceInstance{},
+                               {Scheme: "http", Host: fmt.Sprintf("%s:%s", super.ListenHost, nextPort(super.ListenHost)), Path: "/"}: {},
                        }
                }
        }
index c26309aca5a95fc7d35495295d33f79c72c74cdf..ba8a836dd06aa6a30658a6cf5f7516ca178a815e 100644 (file)
@@ -677,6 +677,10 @@ func (az *azureInstanceSet) manageDisks() {
        }
 
        for ; response.NotDone(); err = response.Next() {
+               if err != nil {
+                       az.logger.WithError(err).Warn("Error getting next page of disks")
+                       return
+               }
                for _, d := range response.Values() {
                        if d.DiskProperties.DiskState == compute.Unattached &&
                                d.Name != nil && re.MatchString(*d.Name) &&
index c392d8638d88d424cb41b8b7b748fda934d5f8d6..6c47a068e98a4c9612e3fe55f69cd9f2d7bdac6b 100644 (file)
@@ -689,6 +689,16 @@ Clusters:
         ProviderAppID: ""
         ProviderAppSecret: ""
 
+      Test:
+        # Authenticate users listed here in the config file. This
+        # feature is intended to be used in test environments, and
+        # should not be used in production.
+        Enable: false
+        Users:
+          SAMPLE:
+            Email: alice@example.com
+            Password: xyzzy
+
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
       # (login cluster must appear in RemoteClusters with Proxy: true)
index 76f00c765251693040d68242f4bb2726e1411990..b203dff26a1b80272be29f41dd747c8108beb272 100644 (file)
@@ -170,6 +170,9 @@ var whitelist = map[string]bool{
        "Login.SSO.Enable":                             true,
        "Login.SSO.ProviderAppID":                      false,
        "Login.SSO.ProviderAppSecret":                  false,
+       "Login.Test":                                   true,
+       "Login.Test.Enable":                            true,
+       "Login.Test.Users":                             false,
        "Login.TokenLifetime":                          false,
        "Mail":                                         true,
        "Mail.EmailFrom":                               false,
index f5004667b23f077750046cd7d0a567832e4e47a8..cbb73d97b81f62f538de77d9b6a7fe563bdc49f7 100644 (file)
@@ -695,6 +695,16 @@ Clusters:
         ProviderAppID: ""
         ProviderAppSecret: ""
 
+      Test:
+        # Authenticate users listed here in the config file. This
+        # feature is intended to be used in test environments, and
+        # should not be used in production.
+        Enable: false
+        Users:
+          SAMPLE:
+            Email: alice@example.com
+            Password: xyzzy
+
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
       # (login cluster must appear in RemoteClusters with Proxy: true)
index 256afc8e6b9482d53eaa520927f62761a1f71b03..5079b402b7208d59bb78c0420b2da547a408b717 100644 (file)
@@ -38,7 +38,7 @@ func (s *FederationSuite) SetUpTest(c *check.C) {
                ClusterID:       "aaaaa",
                SystemRootToken: arvadostest.SystemRootToken,
                RemoteClusters: map[string]arvados.RemoteCluster{
-                       "aaaaa": arvados.RemoteCluster{
+                       "aaaaa": {
                                Host: os.Getenv("ARVADOS_API_HOST"),
                        },
                },
index ee1ea56924c5700d25e43262347d1045d534ca5c..12674148426133fa97f1ef786b38cf6803b1c376 100644 (file)
@@ -33,8 +33,13 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
        wantSSO := cluster.Login.SSO.Enable
        wantPAM := cluster.Login.PAM.Enable
        wantLDAP := cluster.Login.LDAP.Enable
+       wantTest := cluster.Login.Test.Enable
        switch {
-       case wantGoogle && !wantOpenIDConnect && !wantSSO && !wantPAM && !wantLDAP:
+       case 1 != countTrue(wantGoogle, wantOpenIDConnect, wantSSO, wantPAM, wantLDAP, wantTest):
+               return errorLoginController{
+                       error: errors.New("configuration problem: exactly one of Login.Google, Login.OpenIDConnect, Login.SSO, Login.PAM, Login.LDAP, and Login.Test must be enabled"),
+               }
+       case wantGoogle:
                return &oidcLoginController{
                        Cluster:            cluster,
                        RailsProxy:         railsProxy,
@@ -45,7 +50,7 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
                        EmailClaim:         "email",
                        EmailVerifiedClaim: "email_verified",
                }
-       case !wantGoogle && wantOpenIDConnect && !wantSSO && !wantPAM && !wantLDAP:
+       case wantOpenIDConnect:
                return &oidcLoginController{
                        Cluster:            cluster,
                        RailsProxy:         railsProxy,
@@ -56,17 +61,29 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
                        EmailVerifiedClaim: cluster.Login.OpenIDConnect.EmailVerifiedClaim,
                        UsernameClaim:      cluster.Login.OpenIDConnect.UsernameClaim,
                }
-       case !wantGoogle && !wantOpenIDConnect && wantSSO && !wantPAM && !wantLDAP:
+       case wantSSO:
                return &ssoLoginController{railsProxy}
-       case !wantGoogle && !wantOpenIDConnect && !wantSSO && wantPAM && !wantLDAP:
+       case wantPAM:
                return &pamLoginController{Cluster: cluster, RailsProxy: railsProxy}
-       case !wantGoogle && !wantOpenIDConnect && !wantSSO && !wantPAM && wantLDAP:
+       case wantLDAP:
                return &ldapLoginController{Cluster: cluster, RailsProxy: railsProxy}
+       case wantTest:
+               return &testLoginController{Cluster: cluster, RailsProxy: railsProxy}
        default:
                return errorLoginController{
-                       error: errors.New("configuration problem: exactly one of Login.Google, Login.OpenIDConnect, Login.SSO, Login.PAM, and Login.LDAP must be enabled"),
+                       error: errors.New("BUG: missing case in login controller setup switch"),
+               }
+       }
+}
+
+func countTrue(vals ...bool) int {
+       n := 0
+       for _, val := range vals {
+               if val {
+                       n++
                }
        }
+       return n
 }
 
 // Login and Logout are passed through to the wrapped railsProxy;
index 700d757c274d707c703ad0c58dbac812440a45a6..bce1ecfcf260696247bb83de3cdce2fa9d27cabe 100644 (file)
@@ -64,7 +64,7 @@ func (s *LDAPSuite) SetUpSuite(c *check.C) {
                                                return []*godap.LDAPSimpleSearchResultEntry{}
                                        }
                                        return []*godap.LDAPSimpleSearchResultEntry{
-                                               &godap.LDAPSimpleSearchResultEntry{
+                                               {
                                                        DN: "cn=" + req.FilterValue + "," + req.BaseDN,
                                                        Attrs: map[string]interface{}{
                                                                "SN":   req.FilterValue,
diff --git a/lib/controller/localdb/login_testuser.go b/lib/controller/localdb/login_testuser.go
new file mode 100644 (file)
index 0000000..5a3d803
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package localdb
+
+import (
+       "context"
+       "errors"
+       "fmt"
+
+       "git.arvados.org/arvados.git/lib/controller/rpc"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "github.com/sirupsen/logrus"
+)
+
+type testLoginController struct {
+       Cluster    *arvados.Cluster
+       RailsProxy *railsProxy
+}
+
+func (ctrl *testLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
+       return noopLogout(ctrl.Cluster, opts)
+}
+
+func (ctrl *testLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
+       return arvados.LoginResponse{}, errors.New("interactive login is not available")
+}
+
+func (ctrl *testLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
+       for username, user := range ctrl.Cluster.Login.Test.Users {
+               if (opts.Username == username || opts.Username == user.Email) && opts.Password == user.Password {
+                       ctxlog.FromContext(ctx).WithFields(logrus.Fields{
+                               "username": username,
+                               "email":    user.Email,
+                       }).Debug("test authentication succeeded")
+                       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+                               Username: username,
+                               Email:    user.Email,
+                       })
+               }
+       }
+       return arvados.APIClientAuthorization{}, fmt.Errorf("authentication failed for user %q with password len=%d", opts.Username, len(opts.Password))
+}
diff --git a/lib/controller/localdb/login_testuser_test.go b/lib/controller/localdb/login_testuser_test.go
new file mode 100644 (file)
index 0000000..d2d651e
--- /dev/null
@@ -0,0 +1,94 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package localdb
+
+import (
+       "context"
+
+       "git.arvados.org/arvados.git/lib/config"
+       "git.arvados.org/arvados.git/lib/controller/rpc"
+       "git.arvados.org/arvados.git/lib/ctrlctx"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/arvadostest"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "github.com/jmoiron/sqlx"
+       check "gopkg.in/check.v1"
+)
+
+var _ = check.Suite(&TestUserSuite{})
+
+type TestUserSuite struct {
+       cluster  *arvados.Cluster
+       ctrl     *testLoginController
+       railsSpy *arvadostest.Proxy
+       db       *sqlx.DB
+
+       // transaction context
+       ctx      context.Context
+       rollback func() error
+}
+
+func (s *TestUserSuite) SetUpSuite(c *check.C) {
+       cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
+       c.Assert(err, check.IsNil)
+       s.cluster, err = cfg.GetCluster("")
+       c.Assert(err, check.IsNil)
+       s.cluster.Login.Test.Enable = true
+       s.cluster.Login.Test.Users = map[string]arvados.TestUser{
+               "valid": {Email: "valid@example.com", Password: "v@l1d"},
+       }
+       s.railsSpy = arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
+       s.ctrl = &testLoginController{
+               Cluster:    s.cluster,
+               RailsProxy: rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider),
+       }
+       s.db = arvadostest.DB(c, s.cluster)
+}
+
+func (s *TestUserSuite) SetUpTest(c *check.C) {
+       tx, err := s.db.Beginx()
+       c.Assert(err, check.IsNil)
+       s.ctx = ctrlctx.NewWithTransaction(context.Background(), tx)
+       s.rollback = tx.Rollback
+}
+
+func (s *TestUserSuite) TearDownTest(c *check.C) {
+       if s.rollback != nil {
+               s.rollback()
+       }
+}
+
+func (s *TestUserSuite) TestLogin(c *check.C) {
+       for _, trial := range []struct {
+               success  bool
+               username string
+               password string
+       }{
+               {false, "foo", "bar"},
+               {false, "", ""},
+               {false, "valid", ""},
+               {false, "", "v@l1d"},
+               {true, "valid", "v@l1d"},
+               {true, "valid@example.com", "v@l1d"},
+       } {
+               c.Logf("=== %#v", trial)
+               resp, err := s.ctrl.UserAuthenticate(s.ctx, arvados.UserAuthenticateOptions{
+                       Username: trial.username,
+                       Password: trial.password,
+               })
+               if trial.success {
+                       c.Check(err, check.IsNil)
+                       c.Check(resp.APIToken, check.Not(check.Equals), "")
+                       c.Check(resp.UUID, check.Matches, `zzzzz-gj3su-.*`)
+                       c.Check(resp.Scopes, check.DeepEquals, []string{"all"})
+
+                       authinfo := getCallbackAuthInfo(c, s.railsSpy)
+                       c.Check(authinfo.Email, check.Equals, "valid@example.com")
+                       c.Check(authinfo.AlternateEmails, check.DeepEquals, []string(nil))
+               } else {
+                       c.Check(err, check.ErrorMatches, `authentication failed.*`)
+               }
+       }
+}
index aa5f22a501331a2bdd108878987e25b133df720b..42decff31d0cada0bb83ce66ba509aa5f5d13448 100644 (file)
@@ -115,6 +115,7 @@ func (s *DispatcherSuite) TestDispatchToStubDriver(c *check.C) {
                ChooseType: func(ctr *arvados.Container) (arvados.InstanceType, error) {
                        return ChooseInstanceType(s.cluster, ctr)
                },
+               Logger: ctxlog.TestLogger(c),
        }
        for i := 0; i < 200; i++ {
                queue.Containers = append(queue.Containers, arvados.Container{
@@ -170,6 +171,7 @@ func (s *DispatcherSuite) TestDispatchToStubDriver(c *check.C) {
                        stubvm.CrunchRunCrashRate = 0.1
                }
        }
+       s.stubDriver.Bugf = c.Errorf
 
        start := time.Now()
        go s.disp.run()
@@ -303,7 +305,7 @@ func (s *DispatcherSuite) TestInstancesAPI(c *check.C) {
                time.Sleep(time.Millisecond)
        }
        c.Assert(len(sr.Items), check.Equals, 1)
-       c.Check(sr.Items[0].Instance, check.Matches, "stub.*")
+       c.Check(sr.Items[0].Instance, check.Matches, "inst.*")
        c.Check(sr.Items[0].WorkerState, check.Equals, "booting")
        c.Check(sr.Items[0].Price, check.Equals, 0.123)
        c.Check(sr.Items[0].LastContainerUUID, check.Equals, "")
index 4447f084a90cff2f962298c2bb3a71fef851ebb1..dddb974b326fbe7d61c280148e71f4f5c86e7abe 100644 (file)
@@ -88,6 +88,8 @@ tryrun:
                                // a higher-priority container on the
                                // same instance type. Don't let this
                                // one sneak in ahead of it.
+                       } else if sch.pool.KillContainer(ctr.UUID, "about to lock") {
+                               logger.Info("not restarting yet: crunch-run process from previous attempt has not exited")
                        } else if sch.pool.StartContainer(it, ctr) {
                                // Success.
                        } else {
index 32c6b3b24d198b90adb5f2899580783beb2dd9cb..992edddfba6370198a16def5a6b57aed18575aa4 100644 (file)
@@ -83,8 +83,9 @@ func (p *stubPool) ForgetContainer(uuid string) {
 func (p *stubPool) KillContainer(uuid, reason string) bool {
        p.Lock()
        defer p.Unlock()
-       delete(p.running, uuid)
-       return true
+       defer delete(p.running, uuid)
+       t, ok := p.running[uuid]
+       return ok && t.IsZero()
 }
 func (p *stubPool) Shutdown(arvados.InstanceType) bool {
        p.shutdowns++
index 116ca7643117d3f4df3b6e8d4e99864a44d6dfe6..fc683505f93dbae41ff42f31032dd2d145d72169 100644 (file)
@@ -109,13 +109,17 @@ func (sch *Scheduler) cancel(uuid string, reason string) {
 }
 
 func (sch *Scheduler) kill(uuid string, reason string) {
+       if !sch.uuidLock(uuid, "kill") {
+               return
+       }
+       defer sch.uuidUnlock(uuid)
        sch.pool.KillContainer(uuid, reason)
        sch.pool.ForgetContainer(uuid)
 }
 
 func (sch *Scheduler) requeue(ent container.QueueEnt, reason string) {
        uuid := ent.Container.UUID
-       if !sch.uuidLock(uuid, "cancel") {
+       if !sch.uuidLock(uuid, "requeue") {
                return
        }
        defer sch.uuidUnlock(uuid)
index 11d410fb1b9a931b8b65cb990aea1298babf7269..74b84122f286d912d8f8ef392e3eb860e6b1831d 100644 (file)
@@ -11,6 +11,7 @@ import (
 
        "git.arvados.org/arvados.git/lib/dispatchcloud/container"
        "git.arvados.org/arvados.git/sdk/go/arvados"
+       "github.com/sirupsen/logrus"
 )
 
 // Queue is a test stub for container.Queue. The caller specifies the
@@ -23,6 +24,8 @@ type Queue struct {
        // must not be nil.
        ChooseType func(*arvados.Container) (arvados.InstanceType, error)
 
+       Logger logrus.FieldLogger
+
        entries     map[string]container.QueueEnt
        updTime     time.Time
        subscribers map[<-chan struct{}]chan struct{}
@@ -166,13 +169,36 @@ func (q *Queue) Notify(upd arvados.Container) bool {
        defer q.mtx.Unlock()
        for i, ctr := range q.Containers {
                if ctr.UUID == upd.UUID {
-                       if ctr.State != arvados.ContainerStateComplete && ctr.State != arvados.ContainerStateCancelled {
+                       if allowContainerUpdate[ctr.State][upd.State] {
                                q.Containers[i] = upd
                                return true
+                       } else {
+                               if q.Logger != nil {
+                                       q.Logger.WithField("ContainerUUID", ctr.UUID).Infof("test.Queue rejected update from %s to %s", ctr.State, upd.State)
+                               }
+                               return false
                        }
-                       return false
                }
        }
        q.Containers = append(q.Containers, upd)
        return true
 }
+
+var allowContainerUpdate = map[arvados.ContainerState]map[arvados.ContainerState]bool{
+       arvados.ContainerStateQueued: map[arvados.ContainerState]bool{
+               arvados.ContainerStateQueued:    true,
+               arvados.ContainerStateLocked:    true,
+               arvados.ContainerStateCancelled: true,
+       },
+       arvados.ContainerStateLocked: map[arvados.ContainerState]bool{
+               arvados.ContainerStateQueued:    true,
+               arvados.ContainerStateLocked:    true,
+               arvados.ContainerStateRunning:   true,
+               arvados.ContainerStateCancelled: true,
+       },
+       arvados.ContainerStateRunning: map[arvados.ContainerState]bool{
+               arvados.ContainerStateRunning:   true,
+               arvados.ContainerStateCancelled: true,
+               arvados.ContainerStateComplete:  true,
+       },
+}
index 7a1f42301684a5a042f555c3e17ccda5d2f8b6c5..f6e06d3f7cebd05a97c8851f65d8d79812d18297 100644 (file)
@@ -34,6 +34,11 @@ type StubDriver struct {
        // VM's error rate and other behaviors.
        SetupVM func(*StubVM)
 
+       // Bugf, if set, is called if a bug is detected in the caller
+       // or stub. Typically set to (*check.C)Errorf. If unset,
+       // logger.Warnf is called instead.
+       Bugf func(string, ...interface{})
+
        // StubVM's fake crunch-run uses this Queue to read and update
        // container state.
        Queue *Queue
@@ -99,6 +104,7 @@ type StubInstanceSet struct {
 
        allowCreateCall    time.Time
        allowInstancesCall time.Time
+       lastInstanceID     int
 }
 
 func (sis *StubInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, cmd cloud.InitCommand, authKey ssh.PublicKey) (cloud.Instance, error) {
@@ -120,9 +126,10 @@ func (sis *StubInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID,
        if authKey != nil {
                ak = append([]ssh.PublicKey{authKey}, ak...)
        }
+       sis.lastInstanceID++
        svm := &StubVM{
                sis:          sis,
-               id:           cloud.InstanceID(fmt.Sprintf("stub-%s-%x", it.ProviderType, math_rand.Int63())),
+               id:           cloud.InstanceID(fmt.Sprintf("inst%d,%s", sis.lastInstanceID, it.ProviderType)),
                tags:         copyTags(tags),
                providerType: it.ProviderType,
                initCommand:  cmd,
@@ -263,49 +270,68 @@ func (svm *StubVM) Exec(env map[string]string, command string, stdin io.Reader,
                })
                logger.Printf("[test] starting crunch-run stub")
                go func() {
+                       var ctr arvados.Container
+                       var started, completed bool
+                       defer func() {
+                               logger.Print("[test] exiting crunch-run stub")
+                               svm.Lock()
+                               defer svm.Unlock()
+                               if svm.running[uuid] != pid {
+                                       if !completed {
+                                               bugf := svm.sis.driver.Bugf
+                                               if bugf == nil {
+                                                       bugf = logger.Warnf
+                                               }
+                                               bugf("[test] StubDriver bug or caller bug: pid %d exiting, running[%s]==%d", pid, uuid, svm.running[uuid])
+                                       }
+                               } else {
+                                       delete(svm.running, uuid)
+                               }
+                               if !completed {
+                                       logger.WithField("State", ctr.State).Print("[test] crashing crunch-run stub")
+                                       if started && svm.CrashRunningContainer != nil {
+                                               svm.CrashRunningContainer(ctr)
+                                       }
+                               }
+                       }()
+
                        crashluck := math_rand.Float64()
+                       wantCrash := crashluck < svm.CrunchRunCrashRate
+                       wantCrashEarly := crashluck < svm.CrunchRunCrashRate/2
+
                        ctr, ok := queue.Get(uuid)
                        if !ok {
                                logger.Print("[test] container not in queue")
                                return
                        }
 
-                       defer func() {
-                               if ctr.State == arvados.ContainerStateRunning && svm.CrashRunningContainer != nil {
-                                       svm.CrashRunningContainer(ctr)
-                               }
-                       }()
-
-                       if crashluck > svm.CrunchRunCrashRate/2 {
-                               time.Sleep(time.Duration(math_rand.Float64()*20) * time.Millisecond)
-                               ctr.State = arvados.ContainerStateRunning
-                               if !queue.Notify(ctr) {
-                                       ctr, _ = queue.Get(uuid)
-                                       logger.Print("[test] erroring out because state=Running update was rejected")
-                                       return
-                               }
-                       }
-
                        time.Sleep(time.Duration(math_rand.Float64()*20) * time.Millisecond)
 
                        svm.Lock()
-                       defer svm.Unlock()
-                       if svm.running[uuid] != pid {
-                               logger.Print("[test] container was killed")
+                       killed := svm.running[uuid] != pid
+                       svm.Unlock()
+                       if killed || wantCrashEarly {
                                return
                        }
-                       delete(svm.running, uuid)
 
-                       if crashluck < svm.CrunchRunCrashRate {
+                       ctr.State = arvados.ContainerStateRunning
+                       started = queue.Notify(ctr)
+                       if !started {
+                               ctr, _ = queue.Get(uuid)
+                               logger.Print("[test] erroring out because state=Running update was rejected")
+                               return
+                       }
+
+                       if wantCrash {
                                logger.WithField("State", ctr.State).Print("[test] crashing crunch-run stub")
-                       } else {
-                               if svm.ExecuteContainer != nil {
-                                       ctr.ExitCode = svm.ExecuteContainer(ctr)
-                               }
-                               logger.WithField("ExitCode", ctr.ExitCode).Print("[test] exiting crunch-run stub")
-                               ctr.State = arvados.ContainerStateComplete
-                               go queue.Notify(ctr)
+                               return
+                       }
+                       if svm.ExecuteContainer != nil {
+                               ctr.ExitCode = svm.ExecuteContainer(ctr)
                        }
+                       logger.WithField("ExitCode", ctr.ExitCode).Print("[test] completing container")
+                       ctr.State = arvados.ContainerStateComplete
+                       completed = queue.Notify(ctr)
                }()
                return 0
        }
index 86673320da3c41fc394fc8381e0221a47fd2e436..d98ffd18ed154c6882eb64c16f3af47bbc8c4b94 100644 (file)
@@ -177,6 +177,10 @@ type Cluster struct {
                        ProviderAppID     string
                        ProviderAppSecret string
                }
+               Test struct {
+                       Enable bool
+                       Users  map[string]TestUser
+               }
                LoginCluster       string
                RemoteTokenRefresh Duration
                TokenLifetime      Duration
@@ -331,6 +335,11 @@ type Service struct {
        ExternalURL  URL
 }
 
+type TestUser struct {
+       Email    string
+       Password string
+}
+
 // URL is a url.URL that is also usable as a JSON key/value.
 type URL url.URL
 
index 0edc48162b1a031b8487ac9b38997c0a4b6f3f4d..060b57b493cbf555d4d87d9b71db4f6171ff85b8 100644 (file)
@@ -568,8 +568,6 @@ func (fn *filenode) Write(p []byte, startPtr filenodePtr) (n int, ptr filenodePt
                                seg.Truncate(len(cando))
                                fn.memsize += int64(len(cando))
                                fn.segments[cur] = seg
-                               cur++
-                               prev++
                        }
                }
 
@@ -1109,9 +1107,9 @@ func (dn *dirnode) loadManifest(txt string) error {
                                // situation might be rare anyway)
                                segIdx, pos = 0, 0
                        }
-                       for next := int64(0); segIdx < len(segments); segIdx++ {
+                       for ; segIdx < len(segments); segIdx++ {
                                seg := segments[segIdx]
-                               next = pos + int64(seg.Len())
+                               next := pos + int64(seg.Len())
                                if next <= offset || seg.Len() == 0 {
                                        pos = next
                                        continue
index cb2e54bda261ed590b39b59a90235f6b991787a3..86facd681e5aa336ed6c73252ecc9c3936c9502e 100644 (file)
@@ -214,6 +214,7 @@ func (s *SiteFSSuite) TestProjectUpdatedByOther(c *check.C) {
        // Ensure collection was flushed by Sync
        var latest Collection
        err = s.client.RequestAndDecode(&latest, "GET", "arvados/v1/collections/"+oob.UUID, nil, nil)
+       c.Check(err, check.IsNil)
        c.Check(latest.ManifestText, check.Matches, `.*:test.txt.*\n`)
 
        // Delete test.txt behind s.fs's back by updating the
index a1801b21456b9a6d8bbb716f4db19eaa78feaa4a..2604b02b17aaeb412b2519e4c09a69264fa8d340 100644 (file)
@@ -535,6 +535,7 @@ func (s *StandaloneSuite) TestGetEmptyBlock(c *C) {
        defer ks.listener.Close()
 
        arv, err := arvadosclient.MakeArvadosClient()
+       c.Check(err, IsNil)
        kc, _ := MakeKeepClient(arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
index 369294e8a79278ffb437571e74cb726af527e845..b28ae0e0718e2ddabc472f61be8cf8c07a53232f 100644 (file)
@@ -16,7 +16,7 @@ require "sprockets/railtie"
 require "rails/test_unit/railtie"
 # Skipping the following:
 # * ActionCable (new in Rails 5.0) as it adds '/cable' routes that we're not using
-# * Skip ActiveStorage (new in Rails 5.1)
+# * ActiveStorage (new in Rails 5.1)
 
 require 'digest'
 
index 5f3cc608c3a9d360518dda68d47d1cde901cc459..fb0fc0d7830f3582bbe14c27bb45753661770aad 100644 (file)
@@ -54,7 +54,7 @@ func (s *GitoliteSuite) SetUpTest(c *check.C) {
        s.cluster, err = cfg.GetCluster("")
        c.Assert(err, check.Equals, nil)
 
-       s.cluster.Services.GitHTTP.InternalURLs = map[arvados.URL]arvados.ServiceInstance{arvados.URL{Host: "localhost:0"}: arvados.ServiceInstance{}}
+       s.cluster.Services.GitHTTP.InternalURLs = map[arvados.URL]arvados.ServiceInstance{{Host: "localhost:0"}: {}}
        s.cluster.TLS.Insecure = true
        s.cluster.Git.GitCommand = "/usr/share/gitolite3/gitolite-shell"
        s.cluster.Git.GitoliteHome = s.gitoliteHome
diff --git a/services/arv-web/README b/services/arv-web/README
deleted file mode 100644 (file)
index eaf7624..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-arv-web enables you to run a custom web service using the contents of an
-Arvados collection.
-
-See "Using arv-web" in the Arvados user guide:
-
-http://doc.arvados.org/user/topics/arv-web.html
diff --git a/services/arv-web/arv-web.py b/services/arv-web/arv-web.py
deleted file mode 100755 (executable)
index 55b710a..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-# arv-web enables you to run a custom web service from the contents of an Arvados collection.
-#
-# See http://doc.arvados.org/user/topics/arv-web.html
-
-import arvados
-from arvados.safeapi import ThreadSafeApiCache
-import subprocess
-from arvados_fuse import Operations, CollectionDirectory
-import tempfile
-import os
-import llfuse
-import threading
-import Queue
-import argparse
-import logging
-import signal
-import sys
-import functools
-
-logger = logging.getLogger('arvados.arv-web')
-logger.setLevel(logging.INFO)
-
-class ArvWeb(object):
-    def __init__(self, project, docker_image, port):
-        self.project = project
-        self.loop = True
-        self.cid = None
-        self.prev_docker_image = None
-        self.mountdir = None
-        self.collection = None
-        self.override_docker_image = docker_image
-        self.port = port
-        self.evqueue = Queue.Queue()
-        self.api = ThreadSafeApiCache(arvados.config.settings())
-
-        if arvados.util.group_uuid_pattern.match(project) is None:
-            raise arvados.errors.ArgumentError("Project uuid is not valid")
-
-        collections = self.api.collections().list(filters=[["owner_uuid", "=", project]],
-                        limit=1,
-                        order='modified_at desc').execute()['items']
-        self.newcollection = collections[0]['uuid'] if collections else None
-
-        self.ws = arvados.events.subscribe(self.api, [["object_uuid", "is_a", "arvados#collection"]], self.on_message)
-
-    def check_docker_running(self):
-        # It would be less hacky to use "docker events" than poll "docker ps"
-        # but that would require writing a bigger pile of code.
-        if self.cid:
-            ps = subprocess.check_output(["docker", "ps", "--no-trunc=true", "--filter=status=running"])
-            for l in ps.splitlines():
-                if l.startswith(self.cid):
-                    return True
-        return False
-
-    # Handle messages from Arvados event bus.
-    def on_message(self, ev):
-        if 'event_type' in ev:
-            old_attr = None
-            if 'old_attributes' in ev['properties'] and ev['properties']['old_attributes']:
-                old_attr = ev['properties']['old_attributes']
-            if self.project not in (ev['properties']['new_attributes']['owner_uuid'],
-                                    old_attr['owner_uuid'] if old_attr else None):
-                return
-
-            et = ev['event_type']
-            if ev['event_type'] == 'update':
-                if ev['properties']['new_attributes']['owner_uuid'] != ev['properties']['old_attributes']['owner_uuid']:
-                    if self.project == ev['properties']['new_attributes']['owner_uuid']:
-                        et = 'add'
-                    else:
-                        et = 'remove'
-                if ev['properties']['new_attributes']['trash_at'] is not None:
-                    et = 'remove'
-
-            self.evqueue.put((self.project, et, ev['object_uuid']))
-
-    # Run an arvados_fuse mount under the control of the local process.  This lets
-    # us switch out the contents of the directory without having to unmount and
-    # remount.
-    def run_fuse_mount(self):
-        self.mountdir = tempfile.mkdtemp()
-
-        self.operations = Operations(os.getuid(), os.getgid(), self.api, "utf-8")
-        self.cdir = CollectionDirectory(llfuse.ROOT_INODE, self.operations.inodes, self.api, 2, self.collection)
-        self.operations.inodes.add_entry(self.cdir)
-
-        # Initialize the fuse connection
-        llfuse.init(self.operations, self.mountdir, ['allow_other'])
-
-        t = threading.Thread(None, llfuse.main)
-        t.start()
-
-        # wait until the driver is finished initializing
-        self.operations.initlock.wait()
-
-    def mount_collection(self):
-        if self.newcollection != self.collection:
-            self.collection = self.newcollection
-            if not self.mountdir and self.collection:
-                self.run_fuse_mount()
-
-            if self.mountdir:
-                with llfuse.lock:
-                    self.cdir.clear()
-                    # Switch the FUSE directory object so that it stores
-                    # the newly selected collection
-                    if self.collection:
-                        logger.info("Mounting %s", self.collection)
-                    else:
-                        logger.info("Mount is empty")
-                    self.cdir.change_collection(self.collection)
-
-
-    def stop_docker(self):
-        if self.cid:
-            logger.info("Stopping Docker container")
-            subprocess.call(["docker", "stop", self.cid])
-            self.cid = None
-
-    def run_docker(self):
-        try:
-            if self.collection is None:
-                self.stop_docker()
-                return
-
-            docker_image = None
-            if self.override_docker_image:
-                docker_image = self.override_docker_image
-            else:
-                try:
-                    with llfuse.lock:
-                        if "docker_image" in self.cdir:
-                            docker_image = self.cdir["docker_image"].readfrom(0, 1024).strip()
-                except IOError as e:
-                    pass
-
-            has_reload = False
-            try:
-                with llfuse.lock:
-                    has_reload = "reload" in self.cdir
-            except IOError as e:
-                pass
-
-            if docker_image is None:
-                logger.error("Collection must contain a file 'docker_image' or must specify --image on the command line.")
-                self.stop_docker()
-                return
-
-            if docker_image == self.prev_docker_image and self.cid is not None and has_reload:
-                logger.info("Running container reload command")
-                subprocess.check_call(["docker", "exec", self.cid, "/mnt/reload"])
-                return
-
-            self.stop_docker()
-
-            logger.info("Starting Docker container %s", docker_image)
-            self.cid = subprocess.check_output(["docker", "run",
-                                                "--detach=true",
-                                                "--publish=%i:80" % (self.port),
-                                                "--volume=%s:/mnt:ro" % self.mountdir,
-                                                docker_image]).strip()
-
-            self.prev_docker_image = docker_image
-            logger.info("Container id %s", self.cid)
-
-        except subprocess.CalledProcessError:
-            self.cid = None
-
-    def wait_for_events(self):
-        if not self.cid:
-            logger.warning("No service running!  Will wait for a new collection to appear in the project.")
-        else:
-            logger.info("Waiting for events")
-
-        running = True
-        self.loop = True
-        while running:
-            # Main run loop.  Wait on project events, signals, or the
-            # Docker container stopping.
-
-            try:
-                # Poll the queue with a 1 second timeout, if we have no
-                # timeout the Python runtime doesn't have a chance to
-                # process SIGINT or SIGTERM.
-                eq = self.evqueue.get(True, 1)
-                logger.info("%s %s", eq[1], eq[2])
-                self.newcollection = self.collection
-                if eq[1] in ('add', 'update', 'create'):
-                    self.newcollection = eq[2]
-                elif eq[1] == 'remove':
-                    collections = self.api.collections().list(filters=[["owner_uuid", "=", self.project]],
-                                                        limit=1,
-                                                        order='modified_at desc').execute()['items']
-                    self.newcollection = collections[0]['uuid'] if collections else None
-                running = False
-            except Queue.Empty:
-                pass
-
-            if self.cid and not self.check_docker_running():
-                logger.warning("Service has terminated.  Will try to restart.")
-                self.cid = None
-                running = False
-
-
-    def run(self):
-        try:
-            while self.loop:
-                self.loop = False
-                self.mount_collection()
-                try:
-                    self.run_docker()
-                    self.wait_for_events()
-                except (KeyboardInterrupt):
-                    logger.info("Got keyboard interrupt")
-                    self.ws.close()
-                    self.loop = False
-                except Exception as e:
-                    logger.exception("Caught fatal exception, shutting down")
-                    self.ws.close()
-                    self.loop = False
-        finally:
-            self.stop_docker()
-
-            if self.mountdir:
-                logger.info("Unmounting")
-                subprocess.call(["fusermount", "-u", self.mountdir])
-                os.rmdir(self.mountdir)
-
-
-def main(argv):
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--project-uuid', type=str, required=True, help="Project uuid to watch")
-    parser.add_argument('--port', type=int, default=8080, help="Host port to listen on (default 8080)")
-    parser.add_argument('--image', type=str, help="Docker image to run")
-
-    args = parser.parse_args(argv)
-
-    signal.signal(signal.SIGTERM, lambda signal, frame: sys.exit(0))
-
-    try:
-        arvweb = ArvWeb(args.project_uuid, args.image, args.port)
-        arvweb.run()
-    except arvados.errors.ArgumentError as e:
-        logger.error(e)
-        return 1
-
-    return 0
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv[1:]))
diff --git a/services/arv-web/sample-cgi-app/docker_image b/services/arv-web/sample-cgi-app/docker_image
deleted file mode 100644 (file)
index 57f344f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-arvados/arv-web
\ No newline at end of file
diff --git a/services/arv-web/sample-cgi-app/public/.htaccess b/services/arv-web/sample-cgi-app/public/.htaccess
deleted file mode 100644 (file)
index e5145bd..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Options +ExecCGI
-AddHandler cgi-script .cgi
-DirectoryIndex index.cgi
diff --git a/services/arv-web/sample-cgi-app/public/index.cgi b/services/arv-web/sample-cgi-app/public/index.cgi
deleted file mode 100755 (executable)
index 57bc2a9..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/perl
-
-print "Content-type: text/html\n\n";
-print "Hello world from perl!";
diff --git a/services/arv-web/sample-cgi-app/tmp/.keepkeep b/services/arv-web/sample-cgi-app/tmp/.keepkeep
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/services/arv-web/sample-rack-app/config.ru b/services/arv-web/sample-rack-app/config.ru
deleted file mode 100644 (file)
index 65f3c7c..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-app = proc do |env|
-    [200, { "Content-Type" => "text/html" }, ["hello <b>world</b> from ruby"]]
-end
-run app
diff --git a/services/arv-web/sample-rack-app/docker_image b/services/arv-web/sample-rack-app/docker_image
deleted file mode 100644 (file)
index 57f344f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-arvados/arv-web
\ No newline at end of file
diff --git a/services/arv-web/sample-rack-app/public/.keepkeep b/services/arv-web/sample-rack-app/public/.keepkeep
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/services/arv-web/sample-rack-app/tmp/.keepkeep b/services/arv-web/sample-rack-app/tmp/.keepkeep
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/services/arv-web/sample-static-page/docker_image b/services/arv-web/sample-static-page/docker_image
deleted file mode 100644 (file)
index 57f344f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-arvados/arv-web
\ No newline at end of file
diff --git a/services/arv-web/sample-static-page/public/index.html b/services/arv-web/sample-static-page/public/index.html
deleted file mode 100644 (file)
index e8608a5..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<!-- Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: AGPL-3.0 -->
-
-<html>
-  <head><title>arv-web sample</title></head>
-  <body>
-    <p>Hello world static page</p>
-  </body>
-</html>
diff --git a/services/arv-web/sample-static-page/tmp/.keepkeep b/services/arv-web/sample-static-page/tmp/.keepkeep
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/services/arv-web/sample-wsgi-app/docker_image b/services/arv-web/sample-wsgi-app/docker_image
deleted file mode 100644 (file)
index 57f344f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-arvados/arv-web
\ No newline at end of file
diff --git a/services/arv-web/sample-wsgi-app/passenger_wsgi.py b/services/arv-web/sample-wsgi-app/passenger_wsgi.py
deleted file mode 100644 (file)
index faec3c2..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-def application(environ, start_response):
-    start_response('200 OK', [('Content-Type', 'text/plain')])
-    return [b"hello world from python!\n"]
diff --git a/services/arv-web/sample-wsgi-app/public/.keepkeep b/services/arv-web/sample-wsgi-app/public/.keepkeep
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/services/arv-web/sample-wsgi-app/tmp/.keepkeep b/services/arv-web/sample-wsgi-app/tmp/.keepkeep
deleted file mode 100644 (file)
index e69de29..0000000
index 915924e28863c8e5de97af724c60249583c23406..963948cc6bf53cbc86a2568bbc0286310dda5e4b 100644 (file)
@@ -185,10 +185,6 @@ var (
 func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        h.setupOnce.Do(h.setup)
 
-       remoteAddr := r.RemoteAddr
-       if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
-               remoteAddr = xff + "," + remoteAddr
-       }
        if xfp := r.Header.Get("X-Forwarded-Proto"); xfp != "" && xfp != "http" {
                r.URL.Scheme = xfp
        }
index 12e294d9339710f861a491518c5dcb4d541f2f10..01bc8b704788da25b835f9b11ef43db0f079fc26 100644 (file)
@@ -205,6 +205,54 @@ func (h *handler) serveS3(w http.ResponseWriter, r *http.Request) bool {
                }
                w.WriteHeader(http.StatusOK)
                return true
+       case r.Method == http.MethodDelete:
+               if !objectNameGiven || r.URL.Path == "/" {
+                       http.Error(w, "missing object name in DELETE request", http.StatusBadRequest)
+                       return true
+               }
+               fspath := "by_id" + r.URL.Path
+               if strings.HasSuffix(fspath, "/") {
+                       fspath = strings.TrimSuffix(fspath, "/")
+                       fi, err := fs.Stat(fspath)
+                       if os.IsNotExist(err) {
+                               w.WriteHeader(http.StatusNoContent)
+                               return true
+                       } else if err != nil {
+                               http.Error(w, err.Error(), http.StatusInternalServerError)
+                               return true
+                       } else if !fi.IsDir() {
+                               // if "foo" exists and is a file, then
+                               // "foo/" doesn't exist, so we say
+                               // delete was successful.
+                               w.WriteHeader(http.StatusNoContent)
+                               return true
+                       }
+               } else if fi, err := fs.Stat(fspath); err == nil && fi.IsDir() {
+                       // if "foo" is a dir, it is visible via S3
+                       // only as "foo/", not "foo" -- so we leave
+                       // the dir alone and return 204 to indicate
+                       // that "foo" does not exist.
+                       w.WriteHeader(http.StatusNoContent)
+                       return true
+               }
+               err = fs.Remove(fspath)
+               if os.IsNotExist(err) {
+                       w.WriteHeader(http.StatusNoContent)
+                       return true
+               }
+               if err != nil {
+                       err = fmt.Errorf("rm failed: %w", err)
+                       http.Error(w, err.Error(), http.StatusBadRequest)
+                       return true
+               }
+               err = fs.Sync()
+               if err != nil {
+                       err = fmt.Errorf("sync failed: %w", err)
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return true
+               }
+               w.WriteHeader(http.StatusNoContent)
+               return true
        default:
                http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
                return true
index 73553ff4d31b4ff7538eaecbff56c94711ad57bb..b82f1efd7818b1fd26f5bbe6ffad5cc9f5fff5ec 100644 (file)
@@ -260,6 +260,43 @@ func (s *IntegrationSuite) TestS3ProjectPutObjectNotSupported(c *check.C) {
        }
 }
 
+func (s *IntegrationSuite) TestS3CollectionDeleteObject(c *check.C) {
+       stage := s.s3setup(c)
+       defer stage.teardown(c)
+       s.testS3DeleteObject(c, stage.collbucket, "")
+}
+func (s *IntegrationSuite) TestS3ProjectDeleteObject(c *check.C) {
+       stage := s.s3setup(c)
+       defer stage.teardown(c)
+       s.testS3DeleteObject(c, stage.projbucket, stage.coll.Name+"/")
+}
+func (s *IntegrationSuite) testS3DeleteObject(c *check.C, bucket *s3.Bucket, prefix string) {
+       s.testServer.Config.cluster.Collections.S3FolderObjects = true
+       for _, trial := range []struct {
+               path string
+       }{
+               {"/"},
+               {"nonexistentfile"},
+               {"emptyfile"},
+               {"sailboat.txt"},
+               {"sailboat.txt/"},
+               {"emptydir"},
+               {"emptydir/"},
+       } {
+               objname := prefix + trial.path
+               comment := check.Commentf("objname %q", objname)
+
+               err := bucket.Del(objname)
+               if trial.path == "/" {
+                       c.Check(err, check.NotNil)
+                       continue
+               }
+               c.Check(err, check.IsNil, comment)
+               _, err = bucket.GetReader(objname)
+               c.Check(err, check.NotNil, comment)
+       }
+}
+
 func (s *IntegrationSuite) TestS3CollectionPutObjectFailure(c *check.C) {
        stage := s.s3setup(c)
        defer stage.teardown(c)
@@ -345,6 +382,7 @@ func (s *IntegrationSuite) TestS3GetBucketVersioning(c *check.C) {
        defer stage.teardown(c)
        for _, bucket := range []*s3.Bucket{stage.collbucket, stage.projbucket} {
                req, err := http.NewRequest("GET", bucket.URL("/"), nil)
+               c.Check(err, check.IsNil)
                req.Header.Set("Authorization", "AWS "+arvadostest.ActiveTokenV2+":none")
                req.URL.RawQuery = "versioning"
                resp, err := http.DefaultClient.Do(req)