Merge branch '15529-federated-user-accounts' refs #15529
authorPeter Amstutz <pamstutz@veritasgenetics.com>
Thu, 5 Sep 2019 18:38:20 +0000 (14:38 -0400)
committerPeter Amstutz <pamstutz@veritasgenetics.com>
Thu, 5 Sep 2019 18:38:20 +0000 (14:38 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz@veritasgenetics.com>

51 files changed:
apps/workbench/app/controllers/actions_controller.rb
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/controllers/container_requests_controller.rb
apps/workbench/app/controllers/groups_controller.rb
apps/workbench/app/controllers/jobs_controller.rb
apps/workbench/app/controllers/pipeline_instances_controller.rb
apps/workbench/app/controllers/projects_controller.rb
apps/workbench/app/controllers/repositories_controller.rb
apps/workbench/app/controllers/trash_items_controller.rb
apps/workbench/app/controllers/users_controller.rb
apps/workbench/app/controllers/virtual_machines_controller.rb
apps/workbench/app/controllers/work_unit_templates_controller.rb
apps/workbench/app/controllers/work_units_controller.rb
apps/workbench/app/helpers/pipeline_instances_helper.rb
apps/workbench/app/models/container_work_unit.rb
apps/workbench/app/models/pipeline_instance.rb
apps/workbench/app/models/pipeline_instance_work_unit.rb
doc/admin/config-migration.html.textile.liquid
doc/admin/upgrading.html.textile.liquid
doc/install/install-arv-git-httpd.html.textile.liquid
doc/install/install-keepproxy.html.textile.liquid
lib/config/config.default.yml
lib/config/deprecated.go
lib/config/deprecated_test.go
lib/config/export.go
lib/config/generated_config.go
lib/config/load.go
sdk/go/arvados/config.go
sdk/python/arvados/util.py
sdk/python/tests/run_test_server.py
services/arv-git-httpd/arvados-git-httpd.service
services/arv-git-httpd/auth_handler.go
services/arv-git-httpd/auth_handler_test.go
services/arv-git-httpd/git_handler.go
services/arv-git-httpd/git_handler_test.go
services/arv-git-httpd/gitolite_test.go
services/arv-git-httpd/integration_test.go
services/arv-git-httpd/main.go
services/arv-git-httpd/server.go
services/arv-git-httpd/usage.go [deleted file]
services/keepproxy/keepproxy.go
services/keepproxy/keepproxy.service
services/keepproxy/keepproxy_test.go
services/keepproxy/usage.go [deleted file]
tools/arvbox/lib/arvbox/docker/cluster-config.sh
tools/arvbox/lib/arvbox/docker/common.sh
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service
tools/arvbox/lib/arvbox/docker/service/keepproxy/run-service
tools/arvbox/lib/arvbox/docker/service/nginx/run
vendor/vendor.json

index e6f20be37064ea7f396206ca0b7d68ae3482ef09..376465ee12e09f3c6337416bfc363b0ae9e0fa18 100644 (file)
@@ -86,7 +86,7 @@ class ActionsController < ApplicationController
               end
             end
             if resource_class == Collection
-              dst.manifest_text = Collection.select([:manifest_text]).where(uuid: src.uuid).first.manifest_text
+              dst.manifest_text = Collection.select([:manifest_text]).where(uuid: src.uuid).with_count("none").first.manifest_text
             end
           when :move
             dst = src
@@ -122,7 +122,7 @@ class ActionsController < ApplicationController
     uuids, source_paths = selected_collection_files params
 
     new_coll = Arv::Collection.new
-    Collection.where(uuid: uuids.uniq).
+    Collection.where(uuid: uuids.uniq).with_count("none").
         select([:uuid, :manifest_text]).each do |coll|
       src_coll = Arv::Collection.new(coll.manifest_text)
       src_pathlist = source_paths[coll.uuid]
index ccd0abdc33e5245c2c05014e5d7cd176f297961b..63112984ca957ecc52d8f7ba1315a218de977b1f 100644 (file)
@@ -499,7 +499,7 @@ class ApplicationController < ActionController::Base
   def is_starred
     links = Link.where(tail_uuid: current_user.uuid,
                head_uuid: @object.uuid,
-               link_class: 'star')
+               link_class: 'star').with_count("none")
 
     return links.andand.any?
   end
@@ -782,7 +782,7 @@ class ApplicationController < ActionController::Base
 
   @@notification_tests.push lambda { |controller, current_user|
     return nil if Rails.configuration.Services.WebShell.ExternalURL != URI("")
-    AuthorizedKey.limit(1).where(authorized_user_uuid: current_user.uuid).each do
+    AuthorizedKey.limit(1).with_count('none').where(authorized_user_uuid: current_user.uuid).each do
       return nil
     end
     return lambda { |view|
@@ -791,7 +791,7 @@ class ApplicationController < ActionController::Base
   }
 
   @@notification_tests.push lambda { |controller, current_user|
-    Collection.limit(1).where(created_by: current_user.uuid).each do
+    Collection.limit(1).with_count('none').where(created_by: current_user.uuid).each do
       return nil
     end
     return lambda { |view|
@@ -801,7 +801,7 @@ class ApplicationController < ActionController::Base
 
   @@notification_tests.push lambda { |controller, current_user|
     if PipelineInstance.api_exists?(:index)
-      PipelineInstance.limit(1).where(created_by: current_user.uuid).each do
+      PipelineInstance.limit(1).with_count('none').where(created_by: current_user.uuid).each do
         return nil
       end
     else
@@ -862,7 +862,7 @@ class ApplicationController < ActionController::Base
   helper_method :recent_jobs_and_pipelines
   def recent_jobs_and_pipelines
     (Job.limit(10) |
-     PipelineInstance.limit(10)).
+     PipelineInstance.limit(10).with_count("none")).
       sort_by do |x|
       (x.finished_at || x.started_at rescue nil) || x.modified_at || x.created_at
     end.reverse
@@ -870,7 +870,7 @@ class ApplicationController < ActionController::Base
 
   helper_method :running_pipelines
   def running_pipelines
-    pi = PipelineInstance.order(["started_at asc", "created_at asc"]).filter([["state", "in", ["RunningOnServer", "RunningOnClient"]]])
+    pi = PipelineInstance.order(["started_at asc", "created_at asc"]).with_count("none").filter([["state", "in", ["RunningOnServer", "RunningOnClient"]]])
     jobs = {}
     pi.each do |pl|
       pl.components.each do |k,v|
@@ -881,7 +881,7 @@ class ApplicationController < ActionController::Base
     end
 
     if jobs.keys.any?
-      Job.filter([["uuid", "in", jobs.keys]]).each do |j|
+      Job.filter([["uuid", "in", jobs.keys]]).with_count("none").each do |j|
         jobs[j[:uuid]] = j
       end
 
@@ -904,11 +904,11 @@ class ApplicationController < ActionController::Base
     procs = {}
     if PipelineInstance.api_exists?(:index)
       cols = %w(uuid owner_uuid created_at modified_at pipeline_template_uuid name state started_at finished_at)
-      pipelines = PipelineInstance.select(cols).limit(lim).order(["created_at desc"])
+      pipelines = PipelineInstance.select(cols).limit(lim).order(["created_at desc"]).with_count("none")
       pipelines.results.each { |pi| procs[pi] = pi.created_at }
     end
 
-    crs = ContainerRequest.limit(lim).order(["created_at desc"]).filter([["requesting_container_uuid", "=", nil]])
+    crs = ContainerRequest.limit(lim).with_count("none").order(["created_at desc"]).filter([["requesting_container_uuid", "=", nil]])
     crs.results.each { |c| procs[c] = c.created_at }
 
     Hash[procs.sort_by {|key, value| value}].keys.reverse.first(lim)
@@ -916,9 +916,9 @@ class ApplicationController < ActionController::Base
 
   helper_method :recent_collections
   def recent_collections lim
-    c = Collection.limit(lim).order(["modified_at desc"]).results
+    c = Collection.limit(lim).with_count("none").order(["modified_at desc"]).results
     own = {}
-    Group.filter([["uuid", "in", c.map(&:owner_uuid)]]).each do |g|
+    Group.filter([["uuid", "in", c.map(&:owner_uuid)]]).with_count("none").each do |g|
       own[g[:uuid]] = g
     end
     {collections: c, owners: own}
@@ -929,9 +929,9 @@ class ApplicationController < ActionController::Base
     return if defined?(@starred_projects) && @starred_projects
     links = Link.filter([['tail_uuid', '=', user.uuid],
                          ['link_class', '=', 'star'],
-                         ['head_uuid', 'is_a', 'arvados#group']]).select(%w(head_uuid))
+                         ['head_uuid', 'is_a', 'arvados#group']]).with_count("none").select(%w(head_uuid))
     uuids = links.collect { |x| x.head_uuid }
-    starred_projects = Group.filter([['uuid', 'in', uuids]]).order('name')
+    starred_projects = Group.filter([['uuid', 'in', uuids]]).order('name').with_count("none")
     @starred_projects = starred_projects.results
   end
 
@@ -1084,7 +1084,7 @@ class ApplicationController < ActionController::Base
     end
 
     # TODO: make sure we get every page of results from API server
-    Link.filter([['head_uuid', 'in', uuids]]).each do |link|
+    Link.filter([['head_uuid', 'in', uuids]]).with_count("none").each do |link|
       @all_links_for[link.head_uuid] << link
     end
     @all_links_for
@@ -1137,7 +1137,7 @@ class ApplicationController < ActionController::Base
     end
 
     # TODO: make sure we get every page of results from API server
-    Collection.where(uuid: uuids).each do |collection|
+    Collection.where(uuid: uuids).with_count("none").each do |collection|
       @all_collections_for[collection.uuid] << collection
     end
     @all_collections_for
@@ -1187,7 +1187,7 @@ class ApplicationController < ActionController::Base
     end
 
     # TODO: make sure we get every page of results from API server
-    Collection.where(uuid: uuids).each do |collection|
+    Collection.where(uuid: uuids).with_count("none").each do |collection|
       @all_log_collections_for[collection.uuid] << collection
     end
     @all_log_collections_for
@@ -1220,7 +1220,7 @@ class ApplicationController < ActionController::Base
       @all_pdhs_for[x] = []
     end
 
-    Collection.select(%w(portable_data_hash)).where(portable_data_hash: pdhs).distinct().each do |collection|
+    Collection.select(%w(portable_data_hash)).where(portable_data_hash: pdhs).distinct().with_count("none").each do |collection|
       @all_pdhs_for[collection.portable_data_hash] << collection
     end
     @all_pdhs_for
@@ -1291,7 +1291,7 @@ class ApplicationController < ActionController::Base
     end
 
     unless link_uuids.empty?
-      Link.select([:head_uuid]).where(uuid: link_uuids).each do |link|
+      Link.select([:head_uuid]).where(uuid: link_uuids).with_count("none").each do |link|
         if ArvadosBase::resource_class_for_uuid(link.head_uuid) == Collection
           coll_ids << link.head_uuid
         end
@@ -1314,7 +1314,7 @@ class ApplicationController < ActionController::Base
     end
 
     unless pdhs.empty?
-      Collection.where(portable_data_hash: pdhs.uniq).
+      Collection.where(portable_data_hash: pdhs.uniq).with_count("none").
           select([:uuid, :portable_data_hash]).each do |coll|
         unless source_paths[coll.portable_data_hash].empty?
           uuids << coll.uuid
index de20b8858ac6c5857eed076c507680ee478fab9c..10e026ae6c80578e9cd9c933cbb188bd57f1ca94 100644 (file)
@@ -36,7 +36,7 @@ class CollectionsController < ApplicationController
                                    ['link_class', '=', 'resources'],
                                    ['name', '=', 'wants'],
                                    ['tail_uuid', '=', current_user.uuid],
-                                   ['head_uuid', '=', @object.uuid]])
+                                   ['head_uuid', '=', @object.uuid]]).with_count("none")
       logger.debug persist_links.inspect
     else
       return unprocessable "Invalid value #{value.inspect}"
@@ -65,7 +65,7 @@ class CollectionsController < ApplicationController
     @select ||= Collection.columns.map(&:name)
     base_search = Collection.select(@select)
     if params[:search].andand.length.andand > 0
-      tags = Link.where(any: ['contains', params[:search]])
+      tags = Link.where(any: ['contains', params[:search]]).with_count("none")
       @objects = (base_search.where(uuid: tags.collect(&:head_uuid)) |
                       base_search.where(any: ['contains', params[:search]])).
         uniq { |c| c.uuid }
@@ -84,7 +84,7 @@ class CollectionsController < ApplicationController
 
       @objects = base_search.limit(limit).offset(offset)
     end
-    @links = Link.where(head_uuid: @objects.collect(&:uuid))
+    @links = Link.where(head_uuid: @objects.collect(&:uuid)).with_count("none")
     @collection_info = {}
     @objects.each do |c|
       @collection_info[c.uuid] = {
@@ -197,19 +197,19 @@ class CollectionsController < ApplicationController
       else
         if Job.api_exists?(:index)
           jobs_with = lambda do |conds|
-            Job.limit(RELATION_LIMIT).where(conds)
+            Job.limit(RELATION_LIMIT).with_count("none").where(conds)
               .results.sort_by { |j| j.finished_at || j.created_at }
           end
           @output_of = jobs_with.call(output: @object.portable_data_hash)
           @log_of = jobs_with.call(log: @object.portable_data_hash)
         end
 
-        @project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
+        @project_links = Link.limit(RELATION_LIMIT).with_count("none").order("modified_at DESC")
           .where(head_uuid: @object.uuid, link_class: 'name').results
-        project_hash = Group.where(uuid: @project_links.map(&:tail_uuid)).to_hash
+        project_hash = Group.where(uuid: @project_links.map(&:tail_uuid)).with_count("none").to_hash
         @projects = project_hash.values
 
-        @permissions = Link.limit(RELATION_LIMIT).order("modified_at DESC")
+        @permissions = Link.limit(RELATION_LIMIT).with_count("none").order("modified_at DESC")
           .where(head_uuid: @object.uuid, link_class: 'permission',
                  name: 'can_read').results
         @search_sharing = search_scopes
index 587819dd442c2c1fbe9a8261b491dfc41cc83742..8ce068198e313cbb172d0fd0b88bf43e50067438 100644 (file)
@@ -20,7 +20,7 @@ class ContainerRequestsController < ApplicationController
 
     # Search for child CRs
     if cr[:container_uuid]
-      child_crs = ContainerRequest.where(requesting_container_uuid: cr[:container_uuid])
+      child_crs = ContainerRequest.where(requesting_container_uuid: cr[:container_uuid]).with_count("none")
 
       child_crs.each do |child|
         nodes[child[:uuid]] = child
@@ -38,14 +38,14 @@ class ContainerRequestsController < ApplicationController
 
     # Batch requests to get all related collections
     # First fetch output collections by UUID.
-    Collection.filter([['uuid', 'in', col_uuids.uniq]]).each do |c|
+    Collection.filter([['uuid', 'in', col_uuids.uniq]]).with_count("none").each do |c|
       output_pdhs << c[:portable_data_hash]
       pdh_to_col[c[:portable_data_hash]] = c
       nodes[c[:uuid]] = c
     end
     # Next, get input collections by PDH.
     Collection.filter(
-      [['portable_data_hash', 'in', col_pdhs - output_pdhs]]).each do |c|
+      [['portable_data_hash', 'in', col_pdhs - output_pdhs]]).with_count("none").each do |c|
       nodes[c[:portable_data_hash]] = c
     end
 
@@ -73,7 +73,7 @@ class ContainerRequestsController < ApplicationController
 
   def cancel
     if @object.container_uuid
-      c = Container.select(['state']).where(uuid: @object.container_uuid).first
+      c = Container.select(['state']).where(uuid: @object.container_uuid).with_count("none").first
       if c && c.state != 'Running'
         # If the container hasn't started yet, setting priority=0
         # leaves our request in "Committed" state and doesn't cancel
index aa78feb9a06f821e1390ab7fbea062d22b497410..5da55be0b5d69a10e5519ac710822ba297aca1ab 100644 (file)
@@ -6,8 +6,8 @@ class GroupsController < ApplicationController
   def index
     @groups = Group.filter [['group_class', '!=', 'project']]
     @group_uuids = @groups.collect &:uuid
-    @links_from = Link.where link_class: 'permission', tail_uuid: @group_uuids
-    @links_to = Link.where link_class: 'permission', head_uuid: @group_uuids
+    @links_from = Link.where(link_class: 'permission', tail_uuid: @group_uuids).with_count("none")
+    @links_to = Link.where(link_class: 'permission', head_uuid: @group_uuids).with_count("none")
     render_index
   end
 
index bac1530d39a23fecf70077fe1e696c749a38b64b..e5c71cb275ed19b21b5db637d49fe7e356b64aea 100644 (file)
@@ -24,11 +24,11 @@ class JobsController < ApplicationController
       nodes[j[:script_version]] = {:uuid => j[:script_version]}
     end
 
-    Collection.where(uuid: collections).each do |c|
+    Collection.where(uuid: collections).with_count("none").each do |c|
       nodes[c[:portable_data_hash]] = c
     end
 
-    Collection.where(portable_data_hash: hashes).each do |c|
+    Collection.where(portable_data_hash: hashes).with_count("none").each do |c|
       nodes[c[:portable_data_hash]] = c
     end
 
index 6431057ddf308855b73d9a582daa348f5c020a6e..81417ff165be51e5fefe5398ac84d8b8d305cdf5 100644 (file)
@@ -134,7 +134,7 @@ class PipelineInstancesController < ApplicationController
 
       jobs = jobs.compact.uniq
       if jobs.any?
-        Job.where(uuid: jobs).each do |j|
+        Job.where(uuid: jobs).with_count("none").each do |j|
           job_uuid = j.uuid
 
           provenance[job_uuid] = j
@@ -158,7 +158,7 @@ class PipelineInstancesController < ApplicationController
 
       hashes = hashes.compact.uniq
       if hashes.any?
-        Collection.where(portable_data_hash: hashes).each do |c|
+        Collection.where(portable_data_hash: hashes).with_count("none").each do |c|
           hash_uuid = c.portable_data_hash
           provenance[hash_uuid] = c
           pips[hash_uuid] = 0 unless pips[hash_uuid] != nil
@@ -168,7 +168,7 @@ class PipelineInstancesController < ApplicationController
 
       collections = collections.compact.uniq
       if collections.any?
-        Collection.where(uuid: collections).each do |c|
+        Collection.where(uuid: collections).with_count("none").each do |c|
           collection_uuid = c.uuid
           provenance[collection_uuid] = c
           pips[collection_uuid] = 0 unless pips[collection_uuid] != nil
index b649b710b01598d73c3c53f19a54df2152dd75aa..66dc3dcea2d418b2bbd79e6907d37fac4cbc0fbb 100644 (file)
@@ -163,7 +163,7 @@ class ProjectsController < ApplicationController
 
   def destroy
     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
-                                  ['tail_uuid','=',@object.uuid]])).any?
+                                  ['tail_uuid','=',@object.uuid]]).with_count("none")).any?
       objects.each do |object|
         object.destroy
       end
index 6ef541ebab23a23159d834ef117692812daa5f9e..953ee1dd2190ee02b7a5a651176ea8a162dbc928 100644 (file)
@@ -50,7 +50,7 @@ class RepositoriesController < ApplicationController
 
     if !owner_filter.andand.any?
       filters = @filters + [["owner_uuid", "=", current_user.uuid]]
-      my_repos = Repository.all.order("name ASC").limit(limit).offset(offset).filter(filters).results
+      my_repos = Repository.all.order("name ASC").limit(limit).with_count("none").offset(offset).filter(filters).results
     else      # done fetching all owned repositories
       my_repos = []
     end
@@ -64,7 +64,7 @@ class RepositoriesController < ApplicationController
     end
 
     filters = @filters + [["owner_uuid", "!=", current_user.uuid]]
-    other_repos = Repository.all.order("name ASC").limit(limit).offset(offset).filter(filters).results
+    other_repos = Repository.all.order("name ASC").limit(limit).with_count("none").offset(offset).filter(filters).results
 
     @objects = (my_repos + other_repos).first(limit)
   end
index 7d6e1431ce2f8f26c2a569d4702a14de1d02abe5..12ef20aa664a337998c1fc5980cea350ece1e982 100644 (file)
@@ -78,10 +78,10 @@ class TrashItemsController < ApplicationController
         base_search = base_search.filter([["modified_at", "<=", last_mod_at], ["uuid", "not in", last_uuids]])
       end
 
-      base_search = base_search.include_trash(true).limit(limit).offset(offset)
+      base_search = base_search.include_trash(true).limit(limit).with_count("none").offset(offset)
 
       if params[:filters].andand.length.andand > 0
-        tags = Link.filter(params[:filters])
+        tags = Link.filter(params[:filters]).with_count("none")
         tagged = []
         if tags.results.length > 0
           tagged = query_on.include_trash(true).where(uuid: tags.collect(&:head_uuid))
@@ -95,14 +95,14 @@ class TrashItemsController < ApplicationController
         owner_uuids = @objects.collect(&:owner_uuid).uniq
         @owners = {}
         @not_trashed = {}
-        Group.filter([["uuid", "in", owner_uuids]]).include_trash(true).each do |grp|
+        Group.filter([["uuid", "in", owner_uuids]]).with_count("none").include_trash(true).each do |grp|
           @owners[grp.uuid] = grp
         end
-        User.filter([["uuid", "in", owner_uuids]]).include_trash(true).each do |grp|
+        User.filter([["uuid", "in", owner_uuids]]).with_count("none").include_trash(true).each do |grp|
           @owners[grp.uuid] = grp
           @not_trashed[grp.uuid] = true
         end
-        Group.filter([["uuid", "in", owner_uuids]]).select([:uuid]).each do |grp|
+        Group.filter([["uuid", "in", owner_uuids]]).with_count("none").select([:uuid]).each do |grp|
           @not_trashed[grp.uuid] = true
         end
       else
index b27482c4cc47351bd474244205c8f60ea634f8ea..febd6e3a1d22ba561b458a9b8bbe86f92466a9eb 100644 (file)
@@ -43,7 +43,7 @@ class UsersController < ApplicationController
 
   def activity
     @breadcrumb_page_name = nil
-    @users = User.limit(params[:limit])
+    @users = User.limit(params[:limit]).with_count("none")
     @user_activity = {}
     @activity = {
       logins: {},
@@ -64,13 +64,13 @@ class UsersController < ApplicationController
         filter([[:event_type, '=', 'login'],
                 [:object_kind, '=', 'arvados#user'],
                 [:created_at, '>=', threshold_start],
-                [:created_at, '<', threshold_end]])
+                [:created_at, '<', threshold_end]]).with_count("none")
       @activity[:jobs][span] = Job.select(%w(uuid modified_by_user_uuid)).
         filter([[:created_at, '>=', threshold_start],
-                [:created_at, '<', threshold_end]])
+                [:created_at, '<', threshold_end]]).with_count("none")
       @activity[:pipeline_instances][span] = PipelineInstance.select(%w(uuid modified_by_user_uuid)).
         filter([[:created_at, '>=', threshold_start],
-                [:created_at, '<', threshold_end]])
+                [:created_at, '<', threshold_end]]).with_count("none")
       @activity.each do |type, act|
         records = act[span]
         @users.each do |u|
@@ -96,7 +96,7 @@ class UsersController < ApplicationController
 
   def storage
     @breadcrumb_page_name = nil
-    @users = User.limit(params[:limit])
+    @users = User.limit(params[:limit]).with_count("none")
     @user_storage = {}
     total_storage = {}
     @log_date = {}
@@ -154,11 +154,13 @@ class UsersController < ApplicationController
     @my_jobs = Job.
       limit(10).
       order('created_at desc').
+      with_count('none').
       where(created_by: current_user.uuid)
 
     @my_collections = Collection.
       limit(10).
       order('created_at desc').
+      with_count('none').
       where(created_by: current_user.uuid)
     collection_uuids = @my_collections.collect &:uuid
 
@@ -168,7 +170,7 @@ class UsersController < ApplicationController
     end
 
     Link.filter([['head_uuid', 'in', collection_uuids],
-                             ['link_class', 'in', ['tag', 'resources']]]).
+                             ['link_class', 'in', ['tag', 'resources']]]).with_count("none")
       each do |link|
       case link.link_class
       when 'tag'
@@ -183,6 +185,7 @@ class UsersController < ApplicationController
     @my_pipelines = PipelineInstance.
       limit(10).
       order('created_at desc').
+      with_count('none').
       where(created_by: current_user.uuid)
 
     respond_to do |f|
@@ -264,14 +267,14 @@ class UsersController < ApplicationController
     @my_vm_logins = {}
     Link.where(tail_uuid: @object.uuid,
                link_class: 'permission',
-               name: 'can_login').
+               name: 'can_login').with_count("none").
           each do |perm_link|
             if perm_link.properties.andand[:username]
               @my_vm_logins[perm_link.head_uuid] ||= []
               @my_vm_logins[perm_link.head_uuid] << perm_link.properties[:username]
             end
           end
-    @my_virtual_machines = VirtualMachine.where(uuid: @my_vm_logins.keys)
+    @my_virtual_machines = VirtualMachine.where(uuid: @my_vm_logins.keys).with_count("none")
   end
 
   def ssh_keys
@@ -335,7 +338,7 @@ class UsersController < ApplicationController
     oid_login_perms = Link.where(tail_uuid: user.email,
                                    head_kind: 'arvados#user',
                                    link_class: 'permission',
-                                   name: 'can_login')
+                                   name: 'can_login').with_count("none")
 
     if oid_login_perms.any?
       prefix_properties = oid_login_perms.first.properties
@@ -346,10 +349,10 @@ class UsersController < ApplicationController
     repo_perms = Link.where(tail_uuid: user.uuid,
                             head_kind: 'arvados#repository',
                             link_class: 'permission',
-                            name: 'can_write')
+                            name: 'can_write').with_count("none")
     if repo_perms.any?
       repo_uuid = repo_perms.first.head_uuid
-      repos = Repository.where(head_uuid: repo_uuid)
+      repos = Repository.where(head_uuid: repo_uuid).with_count("none")
       if repos.any?
         repo_name = repos.first.name
         current_selections[:repo_name] = repo_name
@@ -360,7 +363,7 @@ class UsersController < ApplicationController
     vm_login_perms = Link.where(tail_uuid: user.uuid,
                               head_kind: 'arvados#virtualMachine',
                               link_class: 'permission',
-                              name: 'can_login')
+                              name: 'can_login').with_count("none")
     if vm_login_perms.any?
       vm_perm = vm_login_perms.first
       vm_uuid = vm_perm.head_uuid
index 764571c4c5b635352439fa7d0e09f2d2431e39e9..1427e3cc728e3d30d5399223a3c4e311413a9d48 100644 (file)
@@ -10,7 +10,7 @@ class VirtualMachinesController < ApplicationController
       Link.where(tail_uuid: current_user.uuid,
                  head_uuid: @objects.collect(&:uuid),
                  link_class: 'permission',
-                 name: 'can_login').
+                 name: 'can_login').with_count("none").
         each do |perm_link|
         if perm_link.properties.andand[:username]
           @vm_logins[perm_link.head_uuid] ||= []
index 1dba520a7a8bd69cc415929608c0bcc34ccce0d5..0376590a556d49bd34569828ce7d4ab1e8f9065f 100644 (file)
@@ -12,12 +12,12 @@ class WorkUnitTemplatesController < ApplicationController
     # get next page of pipeline_templates
     if PipelineTemplate.api_exists?(:index)
       filters = @filters + [["uuid", "is_a", ["arvados#pipelineTemplate"]]]
-      pipelines = PipelineTemplate.limit(@limit).order(["created_at desc"]).filter(filters)
+      pipelines = PipelineTemplate.limit(@limit).with_count("none").order(["created_at desc"]).filter(filters)
     end
 
     # get next page of workflows
     filters = @filters + [["uuid", "is_a", ["arvados#workflow"]]]
-    workflows = Workflow.limit(@limit).order(["created_at desc"]).filter(filters)
+    workflows = Workflow.limit(@limit).order(["created_at desc"]).with_count("none").filter(filters)
 
     @objects = (pipelines.to_a + workflows.to_a).sort_by(&:created_at).reverse.first(@limit)
 
index 1ecea99babce40e6755839889d0f6dad6ef26b18..8c4e5e7d9f3f5c64ebb04e4175710c46eefab4ff 100644 (file)
@@ -23,14 +23,14 @@ class WorkUnitsController < ApplicationController
     # get next page of pipeline_instances
     if PipelineInstance.api_exists?(:index)
       filters = @filters + [["uuid", "is_a", ["arvados#pipelineInstance"]]]
-      pipelines = PipelineInstance.limit(@limit).order(["created_at desc"]).filter(filters)
+      pipelines = PipelineInstance.limit(@limit).order(["created_at desc"]).filter(filters).with_count("none")
     end
 
     if params[:show_children]
       # get next page of jobs
       if Job.api_exists?(:index)
         filters = @filters + [["uuid", "is_a", ["arvados#job"]]]
-        jobs = Job.limit(@limit).order(["created_at desc"]).filter(filters)
+        jobs = Job.limit(@limit).order(["created_at desc"]).filter(filters).with_count("none")
       end
     end
 
@@ -39,7 +39,7 @@ class WorkUnitsController < ApplicationController
     if !params[:show_children]
      filters << ["requesting_container_uuid", "=", nil]
     end
-    crs = ContainerRequest.limit(@limit).order(["created_at desc"]).filter(filters)
+    crs = ContainerRequest.limit(@limit).order(["created_at desc"]).filter(filters).with_count("none")
     @objects = (jobs.to_a + pipelines.to_a + crs.to_a).sort_by(&:created_at).reverse.first(@limit)
 
     if @objects.any?
index ac0cbbcccd46451028be3d2acc7afc89c38f839f..8e89331cb6464512cb6991319399dc133b450a16 100644 (file)
@@ -92,7 +92,7 @@ module PipelineInstancesHelper
       c[:job][:uuid] if c.is_a?(Hash) and c[:job].is_a?(Hash)
     }.compact
     job = {}
-    Job.where(uuid: jobuuids).each do |j|
+    Job.where(uuid: jobuuids).with_count("none").each do |j|
       job[j[:uuid]] = j
     end
 
index c564dc12e4381d8f53e896b5281b0307666dc278..faba062d469159748915444a5e9cfe583031b625 100644 (file)
@@ -25,11 +25,11 @@ class ContainerWorkUnit < ProxyWorkUnit
     container_uuid = if @proxied.is_a?(Container) then uuid else get(:container_uuid) end
     if container_uuid
       cols = ContainerRequest.columns.map(&:name) - %w(id updated_at mounts secret_mounts runtime_token)
-      my_children = @child_proxies || ContainerRequest.select(cols).where(requesting_container_uuid: container_uuid).results if !my_children
+      my_children = @child_proxies || ContainerRequest.select(cols).where(requesting_container_uuid: container_uuid).with_count("none").results if !my_children
       my_child_containers = my_children.map(&:container_uuid).compact.uniq
       grandchildren = {}
       my_child_containers.each { |c| grandchildren[c] = []} if my_child_containers.any?
-      reqs = ContainerRequest.select(cols).where(requesting_container_uuid: my_child_containers).results if my_child_containers.any?
+      reqs = ContainerRequest.select(cols).where(requesting_container_uuid: my_child_containers).with_count("none").results if my_child_containers.any?
       reqs.each {|cr| grandchildren[cr.requesting_container_uuid] << cr} if reqs
 
       my_children.each do |cr|
index dd2cc0ab77e20d6ad45e78871f174bec3936acb5..d481f41c7eab37b0f8ba0192ba9e17438fd3803e 100644 (file)
@@ -127,10 +127,10 @@ class PipelineInstance < ArvadosBase
       Keep::Locator.parse(loc_s)
     end
     if log_pdhs.any? and
-        Collection.where(portable_data_hash: log_pdhs).limit(1).results.any?
+        Collection.where(portable_data_hash: log_pdhs).limit(1).with_count("none").results.any?
       true
     elsif log_uuids.any? and
-        Collection.where(uuid: log_uuids).limit(1).results.any?
+        Collection.where(uuid: log_uuids).limit(1).with_count("none").results.any?
       true
     else
       stderr_log_query(1).results.any?
index a9bc9cfb5941d3ee559d6dab0b4131df93f2d48d..1d75f584334ee944837183f27f6efc9f9d66c68d 100644 (file)
@@ -9,7 +9,7 @@ class PipelineInstanceWorkUnit < ProxyWorkUnit
     items = []
 
     jobs = {}
-    results = Job.where(uuid: @proxied.job_ids.values).results
+    results = Job.where(uuid: @proxied.job_ids.values).with_count("none").results
     results.each do |j|
       jobs[j.uuid] = j
     end
index 3f10a87feb29927ed40a694e3d5ceffd85a9c145..4e2fd81afc8ec2cd6d7eb588939d31e3c325ad57 100644 (file)
@@ -62,6 +62,15 @@ h2. keepstore
 
 Currently only reads @RemoteClusters@ from centralized configuration.  Still requires component-specific configuration file.
 
+h2(#keepproxy). keepproxy
+
+The legacy keepproxy config (loaded from @/etc/arvados/keepproxy/keepproxy.yml@ or a different location specified via -legacy-keepproxy-config command line argument) takes precedence over the centralized config. After you migrate everything from the legacy config to the centralized config, you should delete @/etc/arvados/keepproxy/keepproxy.yml@ and stop using the -legacy-keepproxy-config argument.
+
+h2(#arv-git-httpd). arv-git-httpd
+
+The legacy arv-git-httpd config (loaded from @/etc/arvados/git-httpd/git-httpd.yml@ or a different location specified via -legacy-git-httpd-config command line argument) takes precedence over the centralized config. After you migrate everything from the legacy config to the centralized config, you should delete @/etc/arvados/git-httpd/git-httpd.yml@ and stop using the -legacy-git-httpd-config argument.
+
+
 h2. arvados-controller
 
 Already uses centralized config exclusively.  No migration needed.
index 8c2ca765769eb18c6eb79bbe078c0dcde8ba08bc..e1e29ed128fb77ae4d238c6c12911fd35a9b081e 100644 (file)
@@ -41,6 +41,10 @@ table(table table-bordered table-condensed).
 
 h3(#master). development master (as of 2019-08-12)
 
+h4. Arv-git-httpd configuration migration
+
+(feature "#14712":https://dev.arvados.org/issues/14712 ) The arv-git-httpd package can now be configured using the centralized configuration file at @/etc/arvados/config.yml@. Configuration via individual command line arguments is no longer available. Please see "arv-git-httpd's config migration guide":{{site.baseurl}}/admin/config-migration.html#arv-git-httpd for more details.
+
 h4. Keep-web dropped support on command line flags configuration
 
 As we're migrating to a central cluster configuration file, the already deprecated way of getting configurations via environment variables and command line flags isn't valid anymore. Current keep-web supports both the now legacy @keep-web.yml@ config format (used by Arvados 1.4) and the new cluster config file format. Please check "keep-web's install guide":{{site.baseurl}}/install/install-keep-web.html for more details.
@@ -51,6 +55,10 @@ h4. Jobs API is read-only
 
 So that older Arvados sites don't lose access to legacy records, the API has been converted to read-only.  Creating and updating jobs (and related types job_task, pipeline_template and pipeline_instance) is disabled and much of the business logic related has been removed, along with various other code specific to the jobs API.  Specifically, the following programs associated with the jobs API have been removed: @crunch-dispatch.rb@, @crunch-job@, @crunchrunner@, @arv-run-pipeline-instance@, @arv-run@.
 
+h4. Keepproxy configuration migration
+
+(feature "#14715":https://dev.arvados.org/issues/14715 ) Keepproxy can now be configured using the centralized config at @/etc/arvados/config.yml@. Configuration via individual command line arguments is no longer available and the @DisableGet@, @DisablePut@, and @PIDFile@ configuration options are no longer supported. If you are still using the legacy config and @DisableGet@ or @DisablePut@ are set to true or @PIDFile@ has a value, keepproxy will produce an error and fail to start. Please see "keepproxy's config migration guide":{{site.baseurl}}/admin/config-migration.html#keepproxy for more details.
+
 h4. No longer stripping ':' from strings in serialized database columns
 
 (bug "#15311":https://dev.arvados.org/issues/15311 ) Strings read from serialized columns in the database with a leading ':' would have the ':' stripped after loading the record.  This behavior existed due to legacy serialization behavior which stored Ruby symbols with a leading ':'.  Unfortunately this corrupted fields where the leading ":" was intentional.  This behavior has been removed.
index 7fc332177dcf812a1f44f3f5554d9d8583844952..c25fdee1dd3267eb00c038973c945bf41e735c5a 100644 (file)
@@ -212,10 +212,14 @@ Otherwise, create @/etc/cron.d/arvados-git-sync@ with the following content:
 
 h3. Configure the API server to advertise the correct SSH URLs
 
-In your API server's @application.yml@ file, add the following entry:
+Edit the cluster config at @/etc/arvados/config.yml@ and set @Services.GitSSH.ExternalURL@. Replace @uuid_prefix@ with your cluster id.
 
 <notextile>
-<pre><code>git_repo_ssh_base: "git@git.<span class="userinput">uuid_prefix.your.domain</span>:"
+<pre><code>Clusters:
+  <span class="userinput">uuid_prefix</span>:
+    Services:
+      GitSSH:
+        ExternalURL: <span class="userinput">git@git.uuid_prefix.your.domain:</span>
 </code></pre>
 </notextile>
 
@@ -267,21 +271,28 @@ The arvados-git-httpd package includes configuration files for systemd.  If you'
 
 {% include 'notebox_end' %}
 
-Create the configuration file @/etc/arvados/git-httpd/git-httpd.yml@. Run @arvados-git-httpd -h@ to learn more about configuration entries.
+Edit the cluster config at @/etc/arvados/config.yml@ and set the following values. Replace @uuid_prefix@ with your cluster id.
 
 <notextile>
-<pre><code>Client:
-  APIHost: <b>uuid_prefix.your.domain</b>
-  Insecure: false
-GitCommand: /var/lib/arvados/git/gitolite/src/gitolite-shell
-GitoliteHome: /var/lib/arvados/git
-Listen: :9001
-RepoRoot: /var/lib/arvados/git/repositories
+<pre><code>Clusters:
+  <span class="userinput">uuid_prefix</span>:
+    Services:
+      GitHTTP:
+        ExternalURL: <span class="userinput">https://git.uuid_prefix.your.domain/</span>
+        InternalURLs:
+         <span class="userinput">"http://localhost:9001": {}</span>
+    Git:
+      GitCommand: <span class="userinput">/var/lib/arvados/git/gitolite/src/gitolite-shell</span>
+      GitoliteHome: <span class="userinput">/var/lib/arvados/git</span>
+      Repositories: <span class="userinput">/var/lib/arvados/git/repositories</span>
 </code></pre>
 </notextile>
 
+Make sure to include the trailing slash for @Services.GitHTTP.ExternalURL@.
+
 Restart the systemd service to ensure the new configuration is used.
 
+
 <notextile>
 <pre><code>~$ <span class="userinput">sudo systemctl restart arvados-git-httpd</span>
 </code></pre>
@@ -320,17 +331,6 @@ server {
 </code></pre>
 </notextile>
 
-h3. Configure the API server to advertise the correct HTTPS URLs
-
-In your API server's @application.yml@ file, add the following entry:
-
-<notextile>
-<pre><code>git_repo_https_base: https://git.<span class="userinput">uuid_prefix.your.domain</span>/
-</code></pre>
-</notextile>
-
-Make sure to include the trailing slash.
-
 h2. Restart Nginx
 
 Restart Nginx to make the Nginx and API server configuration changes take effect.
index db24953fccb17d35bbf1232f2caa34e19104cea6..d3a60ad0f3dda62fd8fcf270e70e745fd3e9ad49 100644 (file)
@@ -44,27 +44,30 @@ Verify that Keepproxy is functional:
 
 <notextile>
 <pre><code>~$ <span class="userinput">keepproxy -h</span>
-...
-Usage: keepproxy [-config path/to/keepproxy.yml]
-...
+Usage of keepproxy:
+  -config file
+       Site configuration file (default may be overridden by setting an ARVADOS_CONFIG environment variable) (default "/etc/arvados/config.yml")
+  -dump-config
+       write current configuration to stdout and exit
+[...]
+  -version
+       print version information and exit.
 </code></pre>
 </notextile>
 
-h3. Create an API token for the Keepproxy server
-
-{% assign railscmd = "bundle exec ./script/get_anonymous_user_token.rb --get" %}
-{% assign railsout = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" %}
-The Keepproxy server needs a token to talk to the API server.  On the <strong>API server</strong>, use the following command to create the token.  {% include 'install_rails_command' %}
-
-h3. Set up the Keepproxy service
+h3. Update the cluster config
 
-Install runit to supervise the keepproxy daemon.  {% include 'install_runit' %}
-
-The run script for the keepproxy service should set the environment variables @ARVADOS_API_TOKEN@ (with the token you just generated), @ARVADOS_API_HOST@, and, if needed, @ARVADOS_API_HOST_INSECURE@.  The core keepproxy command to run is:
+Edit the cluster config at @/etc/arvados/config.yml@ and set @Services.Keepproxy.ExternalURL@ and @Services.Keepproxy.InternalURLs@.  Replace @uuid_prefix@ with your cluster id.
 
 <notextile>
-<pre><code>ARVADOS_API_TOKEN=<span class="userinput">{{railsout}}</span> ARVADOS_API_HOST=<span class="userinput">uuid_prefix.your.domain</span> exec keepproxy
-</code></pre>
+<pre><code>Clusters:
+  <span class="userinput">uuid_prefix</span>:
+    Services:
+      Keepproxy:
+        ExternalURL: <span class="userinput">https://keep.uuid_prefix.your.domain</span>
+        InternalURLs:
+         <span class="userinput">"http://localhost:25107": {}</span>
+</span></code></pre>
 </notextile>
 
 h3. Set up a reverse proxy with SSL support
@@ -131,6 +134,32 @@ export ARVADOS_API_TOKEN=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
 EOF</span>
 </code></pre></notextile>
 
+h2. Run Keepproxy
+
+h3. Start the service (option 1: systemd)
+
+If your system does not use systemd, skip this section and follow the "runit instructions":#runit instead.
+
+If your system uses systemd, the keepproxy service should already be set up. Start it and check its status:
+
+<notextile>
+<pre><code>~$ <span class="userinput">sudo systemctl restart keepproxy</span>
+~$ <span class="userinput">sudo systemctl status keepproxy</span>
+&#x25cf; keepproxy.service - Arvados Keep Proxy
+   Loaded: loaded (/lib/systemd/system/keepproxy.service; enabled)
+   Active: active (running) since Tue 2019-07-23 09:33:47 EDT; 3 weeks 1 days ago
+     Docs: https://doc.arvados.org/
+ Main PID: 1150 (Keepproxy)
+   CGroup: /system.slice/keepproxy.service
+           â””─1150 /usr/bin/keepproxy
+[...]
+</code></pre>
+</notextile>
+
+h3(#runit). Start the service (option 2: runit)
+
+Install runit to supervise the Keep-web daemon.  {% include 'install_runit' %}
+
 h3. Testing keepproxy
 
 Log into a host that is on an external network from your private Arvados network.  The host should be able to contact your keepproxy server (eg keep.$uuid_prefix.arvadosapi.com), but not your keepstore servers (eg keep[0-9].$uuid_prefix.arvadosapi.com).
index 3338bec511641d03d5b8e9aea458d5a781814e8e..c7a038bec6c5da05fbeef993acae2f31ff7197b9 100644 (file)
@@ -206,6 +206,9 @@ Clusters:
       WebsocketClientEventQueue: 64
       WebsocketServerEventQueue: 4
 
+      # Timeout on requests to internal Keep services.
+      KeepServiceRequestTimeout: 15s
+
     Users:
       # Config parameters to automatically setup new users.  If enabled,
       # this users will be able to self-activate.  Enable this if you want
@@ -425,6 +428,17 @@ Clusters:
       RemoteTokenRefresh: 5m
 
     Git:
+      # Path to git or gitolite-shell executable. Each authenticated
+      # request will execute this program with the single argument "http-backend"
+      GitCommand: /usr/bin/git
+
+      # Path to Gitolite's home directory. If a non-empty path is given,
+      # the CGI environment will be set up to support the use of
+      # gitolite-shell as a GitCommand: for example, if GitoliteHome is
+      # "/gh", then the CGI environment will have GITOLITE_HTTP_HOME=/gh,
+      # PATH=$PATH:/gh/bin, and GL_BYPASS_ACCESS_CHECKS=1.
+      GitoliteHome: ""
+
       # Git repositories must be readable by api server, or you won't be
       # able to submit crunch jobs. To pass the test suites, put a clone
       # of the arvados tree in {git_repositories_dir}/arvados.git or
index 019979d39fe2d068c4a196d398e5111d137c35c1..9eb8c40c18d9455693a9ce18d24d890c528f6d25 100644 (file)
@@ -327,6 +327,78 @@ func (ldr *Loader) loadOldWebsocketConfig(cfg *arvados.Config) error {
        return nil
 }
 
+type oldKeepProxyConfig struct {
+       Client          *arvados.Client
+       Listen          *string
+       DisableGet      *bool
+       DisablePut      *bool
+       DefaultReplicas *int
+       Timeout         *arvados.Duration
+       PIDFile         *string
+       Debug           *bool
+       ManagementToken *string
+}
+
+const defaultKeepproxyConfigPath = "/etc/arvados/keepproxy/keepproxy.yml"
+
+func (ldr *Loader) loadOldKeepproxyConfig(cfg *arvados.Config) error {
+       if ldr.KeepproxyPath == "" {
+               return nil
+       }
+       var oc oldKeepProxyConfig
+       err := ldr.loadOldConfigHelper("keepproxy", ldr.KeepproxyPath, &oc)
+       if os.IsNotExist(err) && ldr.KeepproxyPath == defaultKeepproxyConfigPath {
+               return nil
+       } else if err != nil {
+               return err
+       }
+
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return err
+       }
+
+       loadOldClientConfig(cluster, oc.Client)
+
+       if oc.Listen != nil {
+               cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
+       }
+       if oc.DefaultReplicas != nil {
+               cluster.Collections.DefaultReplication = *oc.DefaultReplicas
+       }
+       if oc.Timeout != nil {
+               cluster.API.KeepServiceRequestTimeout = *oc.Timeout
+       }
+       if oc.Debug != nil {
+               if *oc.Debug && cluster.SystemLogs.LogLevel != "debug" {
+                       cluster.SystemLogs.LogLevel = "debug"
+               } else if !*oc.Debug && cluster.SystemLogs.LogLevel != "info" {
+                       cluster.SystemLogs.LogLevel = "info"
+               }
+       }
+       if oc.ManagementToken != nil {
+               cluster.ManagementToken = *oc.ManagementToken
+       }
+
+       // The following legacy options are no longer supported. If they are set to
+       // true or PIDFile has a value, error out and notify the user
+       unsupportedEntry := func(cfgEntry string) error {
+               return fmt.Errorf("the keepproxy %s configuration option is no longer supported, please remove it from your configuration file", cfgEntry)
+       }
+       if oc.DisableGet != nil && *oc.DisableGet {
+               return unsupportedEntry("DisableGet")
+       }
+       if oc.DisablePut != nil && *oc.DisablePut {
+               return unsupportedEntry("DisablePut")
+       }
+       if oc.PIDFile != nil && *oc.PIDFile != "" {
+               return unsupportedEntry("PIDFile")
+       }
+
+       cfg.Clusters[cluster.ClusterID] = *cluster
+       return nil
+}
+
 const defaultKeepWebConfigPath = "/etc/arvados/keep-web/keep-web.yml"
 
 type oldKeepWebConfig struct {
@@ -396,3 +468,44 @@ func (ldr *Loader) loadOldKeepWebConfig(cfg *arvados.Config) error {
        cfg.Clusters[cluster.ClusterID] = *cluster
        return nil
 }
+
+const defaultGitHttpdConfigPath = "/etc/arvados/git-httpd/git-httpd.yml"
+
+type oldGitHttpdConfig struct {
+       Client          *arvados.Client
+       Listen          string
+       GitCommand      string
+       GitoliteHome    string
+       RepoRoot        string
+       ManagementToken string
+}
+
+func (ldr *Loader) loadOldGitHttpdConfig(cfg *arvados.Config) error {
+       if ldr.GitHttpdPath == "" {
+               return nil
+       }
+       var oc oldGitHttpdConfig
+       err := ldr.loadOldConfigHelper("arv-git-httpd", ldr.GitHttpdPath, &oc)
+       if os.IsNotExist(err) && ldr.GitHttpdPath == defaultGitHttpdConfigPath {
+               return nil
+       } else if err != nil {
+               return err
+       }
+
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return err
+       }
+
+       loadOldClientConfig(cluster, oc.Client)
+
+       cluster.Services.GitHTTP.InternalURLs[arvados.URL{Host: oc.Listen}] = arvados.ServiceInstance{}
+       cluster.TLS.Insecure = oc.Client.Insecure
+       cluster.ManagementToken = oc.ManagementToken
+       cluster.Git.GitCommand = oc.GitCommand
+       cluster.Git.GitoliteHome = oc.GitoliteHome
+       cluster.Git.Repositories = oc.RepoRoot
+
+       cfg.Clusters[cluster.ClusterID] = *cluster
+       return nil
+}
index 8479842be9a3bf1255ff3777373b11468f7bbf8d..5dda0ba94457eb0dd5c76a85a22f38a798011de2 100644 (file)
@@ -6,6 +6,7 @@ package config
 
 import (
        "flag"
+       "fmt"
        "io/ioutil"
        "os"
        "time"
@@ -14,6 +15,35 @@ import (
        check "gopkg.in/check.v1"
 )
 
+func testLoadLegacyConfig(content []byte, mungeFlag string, c *check.C) (*arvados.Cluster, error) {
+       tmpfile, err := ioutil.TempFile("", "example")
+       if err != nil {
+               return nil, err
+       }
+       defer os.Remove(tmpfile.Name())
+
+       if _, err := tmpfile.Write(content); err != nil {
+               return nil, err
+       }
+       if err := tmpfile.Close(); err != nil {
+               return nil, err
+       }
+       flags := flag.NewFlagSet("test", flag.ExitOnError)
+       ldr := testLoader(c, "Clusters: {zzzzz: {}}", nil)
+       ldr.SetupFlags(flags)
+       args := ldr.MungeLegacyConfigArgs(ldr.Logger, []string{"-config", tmpfile.Name()}, mungeFlag)
+       flags.Parse(args)
+       cfg, err := ldr.Load()
+       if err != nil {
+               return nil, err
+       }
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return nil, err
+       }
+       return cluster, nil
+}
+
 func (s *LoadSuite) TestDeprecatedNodeProfilesToServices(c *check.C) {
        hostname, err := os.Hostname()
        c.Assert(err, check.IsNil)
@@ -81,33 +111,8 @@ func (s *LoadSuite) TestLegacyKeepWebConfig(c *check.C) {
        "ManagementToken": "xyzzy"
 }
 `)
-       tmpfile, err := ioutil.TempFile("", "example")
-       if err != nil {
-               c.Error(err)
-       }
-       defer os.Remove(tmpfile.Name())
-
-       if _, err := tmpfile.Write(content); err != nil {
-               c.Error(err)
-       }
-       if err := tmpfile.Close(); err != nil {
-               c.Error(err)
-       }
-       flags := flag.NewFlagSet("keep-web", flag.ExitOnError)
-       ldr := testLoader(c, "Clusters: {zzzzz: {}}", nil)
-       ldr.SetupFlags(flags)
-       args := ldr.MungeLegacyConfigArgs(ldr.Logger, []string{"-config", tmpfile.Name()}, "-legacy-keepweb-config")
-       flags.Parse(args)
-       cfg, err := ldr.Load()
-       if err != nil {
-               c.Error(err)
-       }
-       c.Check(cfg, check.NotNil)
-       cluster, err := cfg.GetCluster("")
-       if err != nil {
-               c.Error(err)
-       }
-       c.Check(cluster, check.NotNil)
+       cluster, err := testLoadLegacyConfig(content, "-legacy-keepweb-config", c)
+       c.Check(err, check.IsNil)
 
        c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
        c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
@@ -127,3 +132,87 @@ func (s *LoadSuite) TestLegacyKeepWebConfig(c *check.C) {
        c.Check(cluster.Users.AnonymousUserToken, check.Equals, "anonusertoken")
        c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
 }
+
+func (s *LoadSuite) TestLegacyKeepproxyConfig(c *check.C) {
+       f := "-legacy-keepproxy-config"
+       content := []byte(fmtKeepproxyConfig("", true))
+       cluster, err := testLoadLegacyConfig(content, f, c)
+
+       c.Check(err, check.IsNil)
+       c.Check(cluster, check.NotNil)
+       c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
+       c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
+       c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
+       c.Check(cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: ":80"}], check.Equals, arvados.ServiceInstance{})
+       c.Check(cluster.Collections.DefaultReplication, check.Equals, 0)
+       c.Check(cluster.API.KeepServiceRequestTimeout.String(), check.Equals, "15s")
+       c.Check(cluster.SystemLogs.LogLevel, check.Equals, "debug")
+
+       content = []byte(fmtKeepproxyConfig("", false))
+       cluster, err = testLoadLegacyConfig(content, f, c)
+       c.Check(cluster.SystemLogs.LogLevel, check.Equals, "info")
+
+       content = []byte(fmtKeepproxyConfig(`"DisableGet": true,`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepproxyConfig(`"DisablePut": true,`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepproxyConfig(`"PIDFile": "test",`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepproxyConfig(`"DisableGet": false, "DisablePut": false, "PIDFile": "",`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.IsNil)
+}
+
+func fmtKeepproxyConfig(param string, debugLog bool) string {
+       return fmt.Sprintf(`
+{
+       "Client": {
+               "Scheme": "",
+               "APIHost": "example.com",
+               "AuthToken": "abcdefg",
+               "Insecure": false
+       },
+       "Listen": ":80",
+       "DefaultReplicas": 0,
+       "Timeout": "15s",
+       "Debug": %t,
+       %s
+       "ManagementToken": "xyzzy"
+}
+`, debugLog, param)
+}
+
+func (s *LoadSuite) TestLegacyArvGitHttpdConfig(c *check.C) {
+       content := []byte(`
+{
+       "Client": {
+               "Scheme": "",
+               "APIHost": "example.com",
+               "AuthToken": "abcdefg",
+       },
+       "Listen": ":9000",
+       "GitCommand": "/test/git",
+       "GitoliteHome": "/test/gitolite",
+       "RepoRoot": "/test/reporoot",
+       "ManagementToken": "xyzzy"
+}
+`)
+       f := "-legacy-git-httpd-config"
+       cluster, err := testLoadLegacyConfig(content, f, c)
+
+       c.Check(err, check.IsNil)
+       c.Check(cluster, check.NotNil)
+       c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
+       c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
+       c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
+       c.Check(cluster.Git.GitCommand, check.Equals, "/test/git")
+       c.Check(cluster.Git.GitoliteHome, check.Equals, "/test/gitolite")
+       c.Check(cluster.Git.Repositories, check.Equals, "/test/reporoot")
+       c.Check(cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: ":9000"}], check.Equals, arvados.ServiceInstance{})
+}
index ffeb527c9487e14ebe1bae6a84a977db91d9101e..69aae2c624a68ed4fcf5837739e33e3d97fc30e7 100644 (file)
@@ -72,6 +72,7 @@ var whitelist = map[string]bool{
        "API.WebsocketClientEventQueue":                false,
        "API.SendTimeout":                              true,
        "API.WebsocketServerEventQueue":                false,
+       "API.KeepServiceRequestTimeout":                false,
        "AuditLogs":                                    false,
        "AuditLogs.MaxAge":                             false,
        "AuditLogs.MaxDeleteBatch":                     false,
index ce1d8a1ed8185ba9e94e6e53ddf3708993796b4d..f8a0e097dc7d4e3f9de577d747cd9c0b54a87d0b 100644 (file)
@@ -212,6 +212,9 @@ Clusters:
       WebsocketClientEventQueue: 64
       WebsocketServerEventQueue: 4
 
+      # Timeout on requests to internal Keep services.
+      KeepServiceRequestTimeout: 15s
+
     Users:
       # Config parameters to automatically setup new users.  If enabled,
       # this users will be able to self-activate.  Enable this if you want
@@ -431,6 +434,17 @@ Clusters:
       RemoteTokenRefresh: 5m
 
     Git:
+      # Path to git or gitolite-shell executable. Each authenticated
+      # request will execute this program with the single argument "http-backend"
+      GitCommand: /usr/bin/git
+
+      # Path to Gitolite's home directory. If a non-empty path is given,
+      # the CGI environment will be set up to support the use of
+      # gitolite-shell as a GitCommand: for example, if GitoliteHome is
+      # "/gh", then the CGI environment will have GITOLITE_HTTP_HOME=/gh,
+      # PATH=$PATH:/gh/bin, and GL_BYPASS_ACCESS_CHECKS=1.
+      GitoliteHome: ""
+
       # Git repositories must be readable by api server, or you won't be
       # able to submit crunch jobs. To pass the test suites, put a clone
       # of the arvados tree in {git_repositories_dir}/arvados.git or
index 3413e3bec3c0418098d39f10c984c20e25c48d69..7e48493939cd67a8322e67fe9f14bf357f26cd76 100644 (file)
@@ -34,6 +34,8 @@ type Loader struct {
        KeepWebPath             string
        CrunchDispatchSlurmPath string
        WebsocketPath           string
+       KeepproxyPath           string
+       GitHttpdPath            string
 
        configdata []byte
 }
@@ -64,6 +66,8 @@ func (ldr *Loader) SetupFlags(flagset *flag.FlagSet) {
        flagset.StringVar(&ldr.KeepWebPath, "legacy-keepweb-config", defaultKeepWebConfigPath, "Legacy keep-web configuration `file`")
        flagset.StringVar(&ldr.CrunchDispatchSlurmPath, "legacy-crunch-dispatch-slurm-config", defaultCrunchDispatchSlurmConfigPath, "Legacy crunch-dispatch-slurm configuration `file`")
        flagset.StringVar(&ldr.WebsocketPath, "legacy-ws-config", defaultWebsocketConfigPath, "Legacy arvados-ws configuration `file`")
+       flagset.StringVar(&ldr.KeepproxyPath, "legacy-keepproxy-config", defaultKeepproxyConfigPath, "Legacy keepproxy configuration `file`")
+       flagset.StringVar(&ldr.GitHttpdPath, "legacy-git-httpd-config", defaultGitHttpdConfigPath, "Legacy arv-git-httpd configuration `file`")
        flagset.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files")
 }
 
@@ -138,6 +142,12 @@ func (ldr *Loader) MungeLegacyConfigArgs(lgr logrus.FieldLogger, args []string,
        if legacyConfigArg != "-legacy-keepweb-config" {
                ldr.KeepWebPath = ""
        }
+       if legacyConfigArg != "-legacy-keepproxy-config" {
+               ldr.KeepproxyPath = ""
+       }
+       if legacyConfigArg != "-legacy-git-httpd-config" {
+               ldr.GitHttpdPath = ""
+       }
 
        return munged
 }
@@ -238,6 +248,8 @@ func (ldr *Loader) Load() (*arvados.Config, error) {
                        ldr.loadOldKeepWebConfig(&cfg),
                        ldr.loadOldCrunchDispatchSlurmConfig(&cfg),
                        ldr.loadOldWebsocketConfig(&cfg),
+                       ldr.loadOldKeepproxyConfig(&cfg),
+                       ldr.loadOldGitHttpdConfig(&cfg),
                } {
                        if err != nil {
                                return nil, err
index fdb3293a671ed18917b2b2a24da4fa5f2d459e3d..29dd62ac1eb2ca3f0c224bd08033284451f45c05 100644 (file)
@@ -88,6 +88,7 @@ type Cluster struct {
                SendTimeout                    Duration
                WebsocketClientEventQueue      int
                WebsocketServerEventQueue      int
+               KeepServiceRequestTimeout      Duration
        }
        AuditLogs struct {
                MaxAge             Duration
@@ -113,6 +114,8 @@ type Cluster struct {
                WebDAVCache WebDAVCacheConfig
        }
        Git struct {
+               GitCommand   string
+               GitoliteHome string
                Repositories string
        }
        Login struct {
index 66da2d12af2082689179bbf1b89dd6285c1edbd9..fd29a3dc1d46938d2ea9e5c661bea2cbf20659be 100644 (file)
@@ -19,6 +19,9 @@ import arvados
 from arvados.collection import CollectionReader
 
 HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
+CR_UNCOMMITTED = 'Uncommitted'
+CR_COMMITTED = 'Committed'
+CR_FINAL = 'Final'
 
 keep_locator_pattern = re.compile(r'[0-9a-f]{32}\+\d+(\+\S+)*')
 signed_locator_pattern = re.compile(r'[0-9a-f]{32}\+\d+(\+\S+)*\+A\S+(\+\S+)*')
index 80227e6cd0a79335e7486beac156c72cde4b8d87..34342059f3062820313127514c47e0061f080a77 100644 (file)
@@ -544,10 +544,11 @@ def run_keep_proxy():
     env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
     logf = open(_logfilename('keepproxy'), 'a')
     kp = subprocess.Popen(
-        ['keepproxy',
-         '-pid='+_pidfile('keepproxy'),
-         '-listen=:{}'.format(port)],
-        env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+        ['keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+
+    with open(_pidfile('keepproxy'), 'w') as f:
+        f.write(str(kp.pid))
+    _wait_until_port_listens(port)
 
     print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
     api = arvados.api(
@@ -577,16 +578,11 @@ def run_arv_git_httpd():
         return
     stop_arv_git_httpd()
 
-    gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
     gitport = internal_port_from_config("GitHTTP")
     env = os.environ.copy()
     env.pop('ARVADOS_API_TOKEN', None)
     logf = open(_logfilename('arv-git-httpd'), 'a')
-    agh = subprocess.Popen(
-        ['arv-git-httpd',
-         '-repo-root='+gitdir+'/test',
-         '-management-token=e687950a23c3a9bceec28c6223a06c79',
-         '-address=:'+str(gitport)],
+    agh = subprocess.Popen(['arv-git-httpd'],
         env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
     with open(_pidfile('arv-git-httpd'), 'w') as f:
         f.write(str(agh.pid))
@@ -746,6 +742,9 @@ def setup_config():
                 },
                 "Collections": {
                     "TrustAllContent": True
+                },
+                "Git": {
+                    "Repositories": "%s/test" % os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
                 }
             }
         }
index 6f8cca856b595498b7f6b8ee387e956e838666a2..7ac160eea33da990b976910de4693f87d97bdbda 100644 (file)
@@ -6,7 +6,6 @@
 Description=Arvados git server
 Documentation=https://doc.arvados.org/
 After=network.target
-AssertPathExists=/etc/arvados/git-httpd/git-httpd.yml
 
 # systemd==229 (ubuntu:xenial) obeys StartLimitInterval in the [Unit] section
 StartLimitInterval=0
index 3b3032afda5d9707616ce474c431e10d2e629e37..6c618189474912bab9fa45062739acff6862ddad 100644 (file)
@@ -14,6 +14,7 @@ import (
        "sync"
        "time"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/auth"
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
@@ -22,14 +23,21 @@ import (
 type authHandler struct {
        handler    http.Handler
        clientPool *arvadosclient.ClientPool
+       cluster    *arvados.Cluster
        setupOnce  sync.Once
 }
 
 func (h *authHandler) setup() {
-       ac, err := arvadosclient.New(&theConfig.Client)
+       client, err := arvados.NewClientFromConfig(h.cluster)
        if err != nil {
                log.Fatal(err)
        }
+
+       ac, err := arvadosclient.New(client)
+       if err != nil {
+               log.Fatalf("Error setting up arvados client prototype %v", err)
+       }
+
        h.clientPool = &arvadosclient.ClientPool{Prototype: ac}
 }
 
@@ -161,7 +169,7 @@ func (h *authHandler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                "/" + repoName + "/.git",
        }
        for _, dir := range tryDirs {
-               if fileInfo, err := os.Stat(theConfig.RepoRoot + dir); err != nil {
+               if fileInfo, err := os.Stat(h.cluster.Git.Repositories + dir); err != nil {
                        if !os.IsNotExist(err) {
                                statusCode, statusText = http.StatusInternalServerError, err.Error()
                                return
@@ -173,7 +181,7 @@ func (h *authHandler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        }
        if rewrittenPath == "" {
                log.Println("WARNING:", repoUUID,
-                       "git directory not found in", theConfig.RepoRoot, tryDirs)
+                       "git directory not found in", h.cluster.Git.Repositories, tryDirs)
                // We say "content not found" to disambiguate from the
                // earlier "API says that repo does not exist" error.
                statusCode, statusText = http.StatusNotFound, "content not found"
index 05fde03e72c7366ebafdf7e1fc04209e86c5868e..568570942e1f143c0f89725bc20cf372512d8983 100644 (file)
@@ -13,6 +13,7 @@ import (
        "path/filepath"
        "strings"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
@@ -20,7 +21,9 @@ import (
 
 var _ = check.Suite(&AuthHandlerSuite{})
 
-type AuthHandlerSuite struct{}
+type AuthHandlerSuite struct {
+       cluster *arvados.Cluster
+}
 
 func (s *AuthHandlerSuite) SetUpSuite(c *check.C) {
        arvadostest.StartAPI()
@@ -34,23 +37,23 @@ func (s *AuthHandlerSuite) SetUpTest(c *check.C) {
        arvadostest.ResetEnv()
        repoRoot, err := filepath.Abs("../api/tmp/git/test")
        c.Assert(err, check.IsNil)
-       theConfig = &Config{
-               Client: arvados.Client{
-                       APIHost:  arvadostest.APIHost(),
-                       Insecure: true,
-               },
-               Listen:          ":0",
-               GitCommand:      "/usr/bin/git",
-               RepoRoot:        repoRoot,
-               ManagementToken: arvadostest.ManagementToken,
-       }
+
+       cfg, err := config.NewLoader(nil, nil).Load()
+       c.Assert(err, check.Equals, nil)
+       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.TLS.Insecure = true
+       s.cluster.Git.GitCommand = "/usr/bin/git"
+       s.cluster.Git.Repositories = repoRoot
 }
 
 func (s *AuthHandlerSuite) TestPermission(c *check.C) {
        h := &authHandler{handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                log.Printf("%v", r.URL)
                io.WriteString(w, r.URL.Path)
-       })}
+       }), cluster: s.cluster}
        baseURL, err := url.Parse("http://git.example/")
        c.Assert(err, check.IsNil)
        for _, trial := range []struct {
@@ -133,7 +136,7 @@ func (s *AuthHandlerSuite) TestPermission(c *check.C) {
 }
 
 func (s *AuthHandlerSuite) TestCORS(c *check.C) {
-       h := &authHandler{}
+       h := &authHandler{cluster: s.cluster}
 
        // CORS preflight
        resp := httptest.NewRecorder()
index d9b08a995b3ab1baee6b3a5b33a3f3f9d084e2b6..0b14e81d54107fb97ff68747bffa58a4ff63de5c 100644 (file)
@@ -10,6 +10,8 @@ import (
        "net/http"
        "net/http/cgi"
        "os"
+
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
 )
 
 // gitHandler is an http.Handler that invokes git-http-backend (or
@@ -20,29 +22,34 @@ type gitHandler struct {
        cgi.Handler
 }
 
-func newGitHandler() http.Handler {
+func newGitHandler(cluster *arvados.Cluster) http.Handler {
        const glBypass = "GL_BYPASS_ACCESS_CHECKS"
        const glHome = "GITOLITE_HTTP_HOME"
        var env []string
        path := os.Getenv("PATH")
-       if theConfig.GitoliteHome != "" {
+       if cluster.Git.GitoliteHome != "" {
                env = append(env,
-                       glHome+"="+theConfig.GitoliteHome,
+                       glHome+"="+cluster.Git.GitoliteHome,
                        glBypass+"=1")
-               path = path + ":" + theConfig.GitoliteHome + "/bin"
+               path = path + ":" + cluster.Git.GitoliteHome + "/bin"
        } else if home, bypass := os.Getenv(glHome), os.Getenv(glBypass); home != "" || bypass != "" {
                env = append(env, glHome+"="+home, glBypass+"="+bypass)
                log.Printf("DEPRECATED: Passing through %s and %s environment variables. Use GitoliteHome configuration instead.", glHome, glBypass)
        }
+
+       var listen arvados.URL
+       for listen = range cluster.Services.GitHTTP.InternalURLs {
+               break
+       }
        env = append(env,
-               "GIT_PROJECT_ROOT="+theConfig.RepoRoot,
+               "GIT_PROJECT_ROOT="+cluster.Git.Repositories,
                "GIT_HTTP_EXPORT_ALL=",
-               "SERVER_ADDR="+theConfig.Listen,
+               "SERVER_ADDR="+listen.Host,
                "PATH="+path)
        return &gitHandler{
                Handler: cgi.Handler{
-                       Path: theConfig.GitCommand,
-                       Dir:  theConfig.RepoRoot,
+                       Path: cluster.Git.GitCommand,
+                       Dir:  cluster.Git.Repositories,
                        Env:  env,
                        Args: []string{"http-backend"},
                },
index 0cf7de4e22c229545bde64edaa03aa9dcb2612a6..d5cb275fd8fea3613584ff809095a5c8094407c5 100644 (file)
@@ -10,18 +10,29 @@ import (
        "net/url"
        "regexp"
 
+       "git.curoverse.com/arvados.git/lib/config"
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        check "gopkg.in/check.v1"
 )
 
 var _ = check.Suite(&GitHandlerSuite{})
 
-type GitHandlerSuite struct{}
+type GitHandlerSuite struct {
+       cluster *arvados.Cluster
+}
 
-func (s *GitHandlerSuite) TestEnvVars(c *check.C) {
-       theConfig = defaultConfig()
-       theConfig.RepoRoot = "/"
-       theConfig.GitoliteHome = "/test/ghh"
+func (s *GitHandlerSuite) SetUpTest(c *check.C) {
+       cfg, err := config.NewLoader(nil, nil).Load()
+       c.Assert(err, check.Equals, nil)
+       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:80"}: arvados.ServiceInstance{}}
+       s.cluster.Git.GitoliteHome = "/test/ghh"
+       s.cluster.Git.Repositories = "/"
+}
+
+func (s *GitHandlerSuite) TestEnvVars(c *check.C) {
        u, err := url.Parse("git.zzzzz.arvadosapi.com/test")
        c.Check(err, check.Equals, nil)
        resp := httptest.NewRecorder()
@@ -30,7 +41,7 @@ func (s *GitHandlerSuite) TestEnvVars(c *check.C) {
                URL:        u,
                RemoteAddr: "[::1]:12345",
        }
-       h := newGitHandler()
+       h := newGitHandler(s.cluster)
        h.(*gitHandler).Path = "/bin/sh"
        h.(*gitHandler).Args = []string{"-c", "printf 'Content-Type: text/plain\r\n\r\n'; env"}
 
@@ -43,7 +54,7 @@ func (s *GitHandlerSuite) TestEnvVars(c *check.C) {
        c.Check(body, check.Matches, `(?ms).*^GL_BYPASS_ACCESS_CHECKS=1$.*`)
        c.Check(body, check.Matches, `(?ms).*^REMOTE_HOST=::1$.*`)
        c.Check(body, check.Matches, `(?ms).*^REMOTE_PORT=12345$.*`)
-       c.Check(body, check.Matches, `(?ms).*^SERVER_ADDR=`+regexp.QuoteMeta(theConfig.Listen)+`$.*`)
+       c.Check(body, check.Matches, `(?ms).*^SERVER_ADDR=`+regexp.QuoteMeta("localhost:80")+`$.*`)
 }
 
 func (s *GitHandlerSuite) TestCGIErrorOnSplitHostPortError(c *check.C) {
@@ -55,7 +66,7 @@ func (s *GitHandlerSuite) TestCGIErrorOnSplitHostPortError(c *check.C) {
                URL:        u,
                RemoteAddr: "test.bad.address.missing.port",
        }
-       h := newGitHandler()
+       h := newGitHandler(s.cluster)
        h.ServeHTTP(resp, req)
        c.Check(resp.Code, check.Equals, http.StatusInternalServerError)
        c.Check(resp.Body.String(), check.Equals, "")
index 88cd221cbf8aaed87e4f91a6289e8dd02458cd90..eaa7b55f8381f0b4097d751b2aad39866babe355 100644 (file)
@@ -10,8 +10,8 @@ import (
        "os/exec"
        "strings"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
 )
 
@@ -47,16 +47,18 @@ func (s *GitoliteSuite) SetUpTest(c *check.C) {
        runGitolite("gitolite", "setup", "--admin", "root")
 
        s.tmpRepoRoot = s.gitoliteHome + "/repositories"
-       s.Config = &Config{
-               Client: arvados.Client{
-                       APIHost:  arvadostest.APIHost(),
-                       Insecure: true,
-               },
-               Listen:       "localhost:0",
-               GitCommand:   "/usr/share/gitolite3/gitolite-shell",
-               GitoliteHome: s.gitoliteHome,
-               RepoRoot:     s.tmpRepoRoot,
-       }
+
+       cfg, err := config.NewLoader(nil, nil).Load()
+       c.Assert(err, check.Equals, nil)
+       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.TLS.Insecure = true
+       s.cluster.Git.GitCommand = "/usr/share/gitolite3/gitolite-shell"
+       s.cluster.Git.GitoliteHome = s.gitoliteHome
+       s.cluster.Git.Repositories = s.tmpRepoRoot
+
        s.IntegrationSuite.SetUpTest(c)
 
        // Install the gitolite hooks in the bare repo we made in
index 53b636dc0e577e75bf5577e66a54059628be8774..46bf8329c2dce429178071a6da72cfb6b90bb8b5 100644 (file)
@@ -12,6 +12,7 @@ import (
        "strings"
        "testing"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
@@ -28,7 +29,7 @@ type IntegrationSuite struct {
        tmpRepoRoot string
        tmpWorkdir  string
        testServer  *server
-       Config      *Config
+       cluster     *arvados.Cluster
 }
 
 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
@@ -41,7 +42,7 @@ func (s *IntegrationSuite) TearDownSuite(c *check.C) {
 
 func (s *IntegrationSuite) SetUpTest(c *check.C) {
        arvadostest.ResetEnv()
-       s.testServer = &server{}
+
        var err error
        if s.tmpRepoRoot == "" {
                s.tmpRepoRoot, err = ioutil.TempDir("", "arv-git-httpd")
@@ -60,6 +61,23 @@ func (s *IntegrationSuite) SetUpTest(c *check.C) {
        _, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && echo work >work && git add work && git -c user.name=Foo -c user.email=Foo commit -am 'workdir: test'").CombinedOutput()
        c.Assert(err, check.Equals, nil)
 
+       if s.cluster == nil {
+               cfg, err := config.NewLoader(nil, nil).Load()
+               c.Assert(err, check.Equals, nil)
+               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.TLS.Insecure = true
+               s.cluster.Git.GitCommand = "/usr/bin/git"
+               s.cluster.Git.Repositories = s.tmpRepoRoot
+               s.cluster.ManagementToken = arvadostest.ManagementToken
+       }
+
+       s.testServer = &server{cluster: s.cluster}
+       err = s.testServer.Start()
+       c.Assert(err, check.Equals, nil)
+
        _, err = exec.Command("git", "config",
                "--file", s.tmpWorkdir+"/.git/config",
                "credential.http://"+s.testServer.Addr+"/.helper",
@@ -71,29 +89,12 @@ func (s *IntegrationSuite) SetUpTest(c *check.C) {
                "none").Output()
        c.Assert(err, check.Equals, nil)
 
-       if s.Config == nil {
-               s.Config = &Config{
-                       Client: arvados.Client{
-                               APIHost:  arvadostest.APIHost(),
-                               Insecure: true,
-                       },
-                       Listen:          "localhost:0",
-                       GitCommand:      "/usr/bin/git",
-                       RepoRoot:        s.tmpRepoRoot,
-                       ManagementToken: arvadostest.ManagementToken,
-               }
-       }
-
        // Clear ARVADOS_API_* env vars before starting up the server,
        // to make sure arv-git-httpd doesn't use them or complain
        // about them being missing.
        os.Unsetenv("ARVADOS_API_HOST")
        os.Unsetenv("ARVADOS_API_HOST_INSECURE")
        os.Unsetenv("ARVADOS_API_TOKEN")
-
-       theConfig = s.Config
-       err = s.testServer.Start()
-       c.Assert(err, check.Equals, nil)
 }
 
 func (s *IntegrationSuite) TearDownTest(c *check.C) {
@@ -116,9 +117,7 @@ func (s *IntegrationSuite) TearDownTest(c *check.C) {
        }
        s.tmpWorkdir = ""
 
-       s.Config = nil
-
-       theConfig = defaultConfig()
+       s.cluster = nil
 }
 
 func (s *IntegrationSuite) RunGit(c *check.C, token, gitCmd, repo string, args ...string) error {
index 74ac7ae55eea05aa396f9ff337a8dbed74505079..3edfcf4ca68454418e62dd29739a16c07f68c242 100644 (file)
@@ -5,89 +5,62 @@
 package main
 
 import (
-       "encoding/json"
        "flag"
        "fmt"
-       "log"
        "os"
-       "regexp"
 
-       "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/config"
+       "git.curoverse.com/arvados.git/lib/config"
        "github.com/coreos/go-systemd/daemon"
+       "github.com/ghodss/yaml"
+       log "github.com/sirupsen/logrus"
 )
 
 var version = "dev"
 
-// Server configuration
-type Config struct {
-       Client          arvados.Client
-       Listen          string
-       GitCommand      string
-       RepoRoot        string
-       GitoliteHome    string
-       ManagementToken string
-}
-
-var theConfig = defaultConfig()
-
-func defaultConfig() *Config {
-       return &Config{
-               Listen:     ":80",
-               GitCommand: "/usr/bin/git",
-               RepoRoot:   "/var/lib/arvados/git/repositories",
-       }
-}
-
 func main() {
-       const defaultCfgPath = "/etc/arvados/git-httpd/git-httpd.yml"
-       const deprecated = " (DEPRECATED -- use config file instead)"
-       flag.StringVar(&theConfig.Listen, "address", theConfig.Listen,
-               "Address to listen on, \"host:port\" or \":port\"."+deprecated)
-       flag.StringVar(&theConfig.GitCommand, "git-command", theConfig.GitCommand,
-               "Path to git or gitolite-shell executable. Each authenticated request will execute this program with a single argument, \"http-backend\"."+deprecated)
-       flag.StringVar(&theConfig.RepoRoot, "repo-root", theConfig.RepoRoot,
-               "Path to git repositories."+deprecated)
-       flag.StringVar(&theConfig.GitoliteHome, "gitolite-home", theConfig.GitoliteHome,
-               "Value for GITOLITE_HTTP_HOME environment variable. If not empty, GL_BYPASS_ACCESS_CHECKS=1 will also be set."+deprecated)
+       logger := log.New()
+       log.SetFormatter(&log.JSONFormatter{
+               TimestampFormat: "2006-01-02T15:04:05.000000000Z07:00",
+       })
 
-       cfgPath := flag.String("config", defaultCfgPath, "Configuration file `path`.")
-       dumpConfig := flag.Bool("dump-config", false, "write current configuration to stdout and exit (useful for migrating from command line flags to config file)")
-       getVersion := flag.Bool("version", false, "print version information and exit.")
+       flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
+       loader := config.NewLoader(os.Stdin, logger)
+       loader.SetupFlags(flags)
 
-       flag.StringVar(&theConfig.ManagementToken, "management-token", theConfig.ManagementToken,
-               "Authorization token to be included in all health check requests.")
+       dumpConfig := flags.Bool("dump-config", false, "write current configuration to stdout and exit (useful for migrating from command line flags to config file)")
+       getVersion := flags.Bool("version", false, "print version information and exit.")
 
-       flag.Usage = usage
-       flag.Parse()
+       args := loader.MungeLegacyConfigArgs(logger, os.Args[1:], "-legacy-git-httpd-config")
+       flags.Parse(args)
 
-       // Print version information if requested
        if *getVersion {
                fmt.Printf("arv-git-httpd %s\n", version)
                return
        }
 
-       err := config.LoadFile(theConfig, *cfgPath)
+       cfg, err := loader.Load()
        if err != nil {
-               h := os.Getenv("ARVADOS_API_HOST")
-               if h == "" || !os.IsNotExist(err) || *cfgPath != defaultCfgPath {
-                       log.Fatal(err)
-               }
-               log.Print("DEPRECATED: No config file found, but ARVADOS_API_HOST environment variable is set. Please use a config file instead.")
-               theConfig.Client.APIHost = h
-               if regexp.MustCompile("^(?i:1|yes|true)$").MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) {
-                       theConfig.Client.Insecure = true
-               }
-               if j, err := json.MarshalIndent(theConfig, "", "    "); err == nil {
-                       log.Print("Current configuration:\n", string(j))
-               }
+               log.Fatal(err)
+       }
+
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               log.Fatal(err)
        }
 
        if *dumpConfig {
-               log.Fatal(config.DumpAndExit(theConfig))
+               out, err := yaml.Marshal(cfg)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               _, err = os.Stdout.Write(out)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               return
        }
 
-       srv := &server{}
+       srv := &server{cluster: cluster}
        if err := srv.Start(); err != nil {
                log.Fatal(err)
        }
@@ -96,7 +69,7 @@ func main() {
        }
        log.Printf("arv-git-httpd %s started", version)
        log.Println("Listening at", srv.Addr)
-       log.Println("Repository root", theConfig.RepoRoot)
+       log.Println("Repository root", cluster.Git.Repositories)
        if err := srv.Wait(); err != nil {
                log.Fatal(err)
        }
index 8f0d90f89e29cad0a0d8d590b66e412b02195ad2..56c9765b56947959792b1ac3e046cfdf5afa2652 100644 (file)
@@ -7,22 +7,30 @@ package main
 import (
        "net/http"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/health"
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
 )
 
 type server struct {
        httpserver.Server
+       cluster *arvados.Cluster
 }
 
 func (srv *server) Start() error {
        mux := http.NewServeMux()
-       mux.Handle("/", &authHandler{handler: newGitHandler()})
+       mux.Handle("/", &authHandler{handler: newGitHandler(srv.cluster), cluster: srv.cluster})
        mux.Handle("/_health/", &health.Handler{
-               Token:  theConfig.ManagementToken,
+               Token:  srv.cluster.ManagementToken,
                Prefix: "/_health/",
        })
+
+       var listen arvados.URL
+       for listen = range srv.cluster.Services.GitHTTP.InternalURLs {
+               break
+       }
+
        srv.Handler = mux
-       srv.Addr = theConfig.Listen
+       srv.Addr = listen.Host
        return srv.Server.Start()
 }
diff --git a/services/arv-git-httpd/usage.go b/services/arv-git-httpd/usage.go
deleted file mode 100644 (file)
index 8863da6..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// arvados-git-httpd provides authenticated access to Arvados-hosted
-// git repositories.
-//
-// See http://doc.arvados.org/install/install-arv-git-httpd.html.
-package main
-
-import (
-       "flag"
-       "fmt"
-       "os"
-
-       "github.com/ghodss/yaml"
-)
-
-func usage() {
-       c := defaultConfig()
-       c.Client.APIHost = "zzzzz.arvadosapi.com:443"
-       exampleConfigFile, err := yaml.Marshal(c)
-       if err != nil {
-               panic(err)
-       }
-       fmt.Fprintf(os.Stderr, `
-
-arvados-git-httpd provides authenticated access to Arvados-hosted git
-repositories.
-
-See http://doc.arvados.org/install/install-arv-git-httpd.html.
-
-Usage: arvados-git-httpd [-config path/to/arvados/git-httpd.yml]
-
-Options:
-`)
-       flag.PrintDefaults()
-       fmt.Fprintf(os.Stderr, `
-Example config file:
-
-%s
-
-Client.APIHost:
-
-    Address (or address:port) of the Arvados API endpoint.
-
-Client.AuthToken:
-
-    Unused. Normally empty, or omitted entirely.
-
-Client.Insecure:
-
-    True if your Arvados API endpoint uses an unverifiable SSL/TLS
-    certificate.
-
-GitCommand:
-
-    Path to git or gitolite-shell executable. Each authenticated
-    request will execute this program with the single argument
-    "http-backend".
-
-GitoliteHome:
-
-    Path to Gitolite's home directory. If a non-empty path is given,
-    the CGI environment will be set up to support the use of
-    gitolite-shell as a GitCommand: for example, if GitoliteHome is
-    "/gh", then the CGI environment will have GITOLITE_HTTP_HOME=/gh,
-    PATH=$PATH:/gh/bin, and GL_BYPASS_ACCESS_CHECKS=1.
-
-Listen:
-
-    Local port to listen on. Can be "address:port" or ":port", where
-    "address" is a host IP address or name and "port" is a port number
-    or name.
-
-RepoRoot:
-
-    Path to git repositories.
-
-`, exampleConfigFile)
-}
index f8aa6c4aa7db3df87e7e598aaa901e8e3e91763c..9244fe00cb165b174005383163b2f24f0a22331e 100644 (file)
@@ -20,9 +20,9 @@ import (
        "syscall"
        "time"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
-       "git.curoverse.com/arvados.git/sdk/go/config"
        "git.curoverse.com/arvados.git/sdk/go/health"
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
@@ -34,25 +34,6 @@ import (
 
 var version = "dev"
 
-type Config struct {
-       Client          arvados.Client
-       Listen          string
-       DisableGet      bool
-       DisablePut      bool
-       DefaultReplicas int
-       Timeout         arvados.Duration
-       PIDFile         string
-       Debug           bool
-       ManagementToken string
-}
-
-func DefaultConfig() *Config {
-       return &Config{
-               Listen:  ":25107",
-               Timeout: arvados.Duration(15 * time.Second),
-       }
-}
-
 var (
        listener net.Listener
        router   http.Handler
@@ -60,114 +41,109 @@ var (
 
 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
 
-func main() {
-       log.SetFormatter(&log.JSONFormatter{
-               TimestampFormat: rfc3339NanoFixed,
-       })
+func configure(logger log.FieldLogger, args []string) (*arvados.Cluster, error) {
+       flags := flag.NewFlagSet(args[0], flag.ExitOnError)
 
-       cfg := DefaultConfig()
+       dumpConfig := flags.Bool("dump-config", false, "write current configuration to stdout and exit")
+       getVersion := flags.Bool("version", false, "Print version information and exit.")
 
-       flagset := flag.NewFlagSet("keepproxy", flag.ExitOnError)
-       flagset.Usage = usage
+       loader := config.NewLoader(os.Stdin, logger)
+       loader.SetupFlags(flags)
 
-       const deprecated = " (DEPRECATED -- use config file instead)"
-       flagset.StringVar(&cfg.Listen, "listen", cfg.Listen, "Local port to listen on."+deprecated)
-       flagset.BoolVar(&cfg.DisableGet, "no-get", cfg.DisableGet, "Disable GET operations."+deprecated)
-       flagset.BoolVar(&cfg.DisablePut, "no-put", cfg.DisablePut, "Disable PUT operations."+deprecated)
-       flagset.IntVar(&cfg.DefaultReplicas, "default-replicas", cfg.DefaultReplicas, "Default number of replicas to write if not specified by the client. If 0, use site default."+deprecated)
-       flagset.StringVar(&cfg.PIDFile, "pid", cfg.PIDFile, "Path to write pid file."+deprecated)
-       timeoutSeconds := flagset.Int("timeout", int(time.Duration(cfg.Timeout)/time.Second), "Timeout (in seconds) on requests to internal Keep services."+deprecated)
-       flagset.StringVar(&cfg.ManagementToken, "management-token", cfg.ManagementToken, "Authorization token to be included in all health check requests.")
-
-       var cfgPath string
-       const defaultCfgPath = "/etc/arvados/keepproxy/keepproxy.yml"
-       flagset.StringVar(&cfgPath, "config", defaultCfgPath, "Configuration file `path`")
-       dumpConfig := flagset.Bool("dump-config", false, "write current configuration to stdout and exit")
-       getVersion := flagset.Bool("version", false, "Print version information and exit.")
-       flagset.Parse(os.Args[1:])
+       args = loader.MungeLegacyConfigArgs(logger, args[1:], "-legacy-keepproxy-config")
+       flags.Parse(args)
 
        // Print version information if requested
        if *getVersion {
                fmt.Printf("keepproxy %s\n", version)
-               return
+               return nil, nil
        }
 
-       err := config.LoadFile(cfg, cfgPath)
+       cfg, err := loader.Load()
        if err != nil {
-               h := os.Getenv("ARVADOS_API_HOST")
-               t := os.Getenv("ARVADOS_API_TOKEN")
-               if h == "" || t == "" || !os.IsNotExist(err) || cfgPath != defaultCfgPath {
-                       log.Fatal(err)
-               }
-               log.Print("DEPRECATED: No config file found, but ARVADOS_API_HOST and ARVADOS_API_TOKEN environment variables are set. Please use a config file instead.")
-               cfg.Client.APIHost = h
-               cfg.Client.AuthToken = t
-               if regexp.MustCompile("^(?i:1|yes|true)$").MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) {
-                       cfg.Client.Insecure = true
+               return nil, err
+       }
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return nil, err
+       }
+
+       if *dumpConfig {
+               out, err := yaml.Marshal(cfg)
+               if err != nil {
+                       return nil, err
                }
-               if y, err := yaml.Marshal(cfg); err == nil && !*dumpConfig {
-                       log.Print("Current configuration:\n", string(y))
+               if _, err := os.Stdout.Write(out); err != nil {
+                       return nil, err
                }
-               cfg.Timeout = arvados.Duration(time.Duration(*timeoutSeconds) * time.Second)
+               return nil, nil
        }
+       return cluster, nil
+}
 
-       if *dumpConfig {
-               log.Fatal(config.DumpAndExit(cfg))
+func main() {
+       logger := log.New()
+       logger.Formatter = &log.JSONFormatter{
+               TimestampFormat: rfc3339NanoFixed,
+       }
+
+       cluster, err := configure(logger, os.Args)
+       if err != nil {
+               log.Fatal(err)
+       }
+       if cluster == nil {
+               return
        }
 
        log.Printf("keepproxy %s started", version)
 
-       arv, err := arvadosclient.New(&cfg.Client)
+       if err := run(logger, cluster); err != nil {
+               log.Fatal(err)
+       }
+
+       log.Println("shutting down")
+}
+
+func run(logger log.FieldLogger, cluster *arvados.Cluster) error {
+       client, err := arvados.NewClientFromConfig(cluster)
+       if err != nil {
+               return err
+       }
+       client.AuthToken = cluster.SystemRootToken
+
+       arv, err := arvadosclient.New(client)
        if err != nil {
-               log.Fatalf("Error setting up arvados client %s", err.Error())
+               return fmt.Errorf("Error setting up arvados client %v", err)
        }
 
-       if cfg.Debug {
+       if cluster.SystemLogs.LogLevel == "debug" {
                keepclient.DebugPrintf = log.Printf
        }
        kc, err := keepclient.MakeKeepClient(arv)
        if err != nil {
-               log.Fatalf("Error setting up keep client %s", err.Error())
+               return fmt.Errorf("Error setting up keep client %v", err)
        }
        keepclient.RefreshServiceDiscoveryOnSIGHUP()
 
-       if cfg.PIDFile != "" {
-               f, err := os.Create(cfg.PIDFile)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               defer f.Close()
-               err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
-               if err != nil {
-                       log.Fatalf("flock(%s): %s", cfg.PIDFile, err)
-               }
-               defer os.Remove(cfg.PIDFile)
-               err = f.Truncate(0)
-               if err != nil {
-                       log.Fatalf("truncate(%s): %s", cfg.PIDFile, err)
-               }
-               _, err = fmt.Fprint(f, os.Getpid())
-               if err != nil {
-                       log.Fatalf("write(%s): %s", cfg.PIDFile, err)
-               }
-               err = f.Sync()
-               if err != nil {
-                       log.Fatalf("sync(%s): %s", cfg.PIDFile, err)
-               }
+       if cluster.Collections.DefaultReplication > 0 {
+               kc.Want_replicas = cluster.Collections.DefaultReplication
        }
 
-       if cfg.DefaultReplicas > 0 {
-               kc.Want_replicas = cfg.DefaultReplicas
+       var listen arvados.URL
+       for listen = range cluster.Services.Keepproxy.InternalURLs {
+               break
        }
 
-       listener, err = net.Listen("tcp", cfg.Listen)
-       if err != nil {
-               log.Fatalf("listen(%s): %s", cfg.Listen, err)
+       var lErr error
+       listener, lErr = net.Listen("tcp", listen.Host)
+       if lErr != nil {
+               return fmt.Errorf("listen(%s): %v", listen.Host, lErr)
        }
+
        if _, err := daemon.SdNotify(false, "READY=1"); err != nil {
                log.Printf("Error notifying init daemon: %v", err)
        }
-       log.Println("Listening at", listener.Addr())
+       log.Println("listening at", listener.Addr())
 
        // Shut down the server gracefully (by closing the listener)
        // if SIGTERM is received.
@@ -181,10 +157,8 @@ func main() {
        signal.Notify(term, syscall.SIGINT)
 
        // Start serving requests.
-       router = MakeRESTRouter(!cfg.DisableGet, !cfg.DisablePut, kc, time.Duration(cfg.Timeout), cfg.ManagementToken)
-       http.Serve(listener, httpserver.AddRequestIDs(httpserver.LogRequests(router)))
-
-       log.Println("shutting down")
+       router = MakeRESTRouter(kc, time.Duration(cluster.API.KeepServiceRequestTimeout), cluster.SystemRootToken)
+       return http.Serve(listener, httpserver.AddRequestIDs(httpserver.LogRequests(router)))
 }
 
 type ApiTokenCache struct {
@@ -292,7 +266,7 @@ type proxyHandler struct {
 
 // MakeRESTRouter returns an http.Handler that passes GET and PUT
 // requests to the appropriate handlers.
-func MakeRESTRouter(enable_get bool, enable_put bool, kc *keepclient.KeepClient, timeout time.Duration, mgmtToken string) http.Handler {
+func MakeRESTRouter(kc *keepclient.KeepClient, timeout time.Duration, mgmtToken string) http.Handler {
        rest := mux.NewRouter()
 
        transport := defaultTransport
@@ -315,24 +289,20 @@ func MakeRESTRouter(enable_get bool, enable_put bool, kc *keepclient.KeepClient,
                },
        }
 
-       if enable_get {
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Get).Methods("GET", "HEAD")
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Get).Methods("GET", "HEAD")
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Get).Methods("GET", "HEAD")
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Get).Methods("GET", "HEAD")
 
-               // List all blocks
-               rest.HandleFunc(`/index`, h.Index).Methods("GET")
+       // List all blocks
+       rest.HandleFunc(`/index`, h.Index).Methods("GET")
 
-               // List blocks whose hash has the given prefix
-               rest.HandleFunc(`/index/{prefix:[0-9a-f]{0,32}}`, h.Index).Methods("GET")
-       }
+       // List blocks whose hash has the given prefix
+       rest.HandleFunc(`/index/{prefix:[0-9a-f]{0,32}}`, h.Index).Methods("GET")
 
-       if enable_put {
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Put).Methods("PUT")
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Put).Methods("PUT")
-               rest.HandleFunc(`/`, h.Put).Methods("POST")
-               rest.HandleFunc(`/{any}`, h.Options).Methods("OPTIONS")
-               rest.HandleFunc(`/`, h.Options).Methods("OPTIONS")
-       }
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Put).Methods("PUT")
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Put).Methods("PUT")
+       rest.HandleFunc(`/`, h.Put).Methods("POST")
+       rest.HandleFunc(`/{any}`, h.Options).Methods("OPTIONS")
+       rest.HandleFunc(`/`, h.Options).Methods("OPTIONS")
 
        rest.Handle("/_health/{check}", &health.Handler{
                Token:  mgmtToken,
index 96dec25ecf77ecc8c3936628829d8dc59aecabc4..1d0113e0e43a04a17fa2e7ae9657f1ed7bc9f1fa 100644 (file)
@@ -6,7 +6,6 @@
 Description=Arvados Keep Proxy
 Documentation=https://doc.arvados.org/
 After=network.target
-AssertPathExists=/etc/arvados/keepproxy/keepproxy.yml
 
 # systemd==229 (ubuntu:xenial) obeys StartLimitInterval in the [Unit] section
 StartLimitInterval=0
index dc70d968e2992a16581694ac70bbf42ba92f93ba..d2758cc25f7ea3f92b35ec461f0d41c87958c72e 100644 (file)
@@ -13,15 +13,17 @@ import (
        "math/rand"
        "net/http"
        "net/http/httptest"
-       "os"
        "strings"
        "sync"
        "testing"
        "time"
 
+       "git.curoverse.com/arvados.git/lib/config"
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       log "github.com/sirupsen/logrus"
 
        . "gopkg.in/check.v1"
 )
@@ -96,14 +98,23 @@ func (s *NoKeepServerSuite) TearDownSuite(c *C) {
        arvadostest.StopAPI()
 }
 
-func runProxy(c *C, args []string, bogusClientToken bool) *keepclient.KeepClient {
-       args = append([]string{"keepproxy"}, args...)
-       os.Args = append(args, "-listen=:0")
+func runProxy(c *C, bogusClientToken bool) *keepclient.KeepClient {
+       cfg, err := config.NewLoader(nil, nil).Load()
+       c.Assert(err, Equals, nil)
+       cluster, err := cfg.GetCluster("")
+       c.Assert(err, Equals, nil)
+
+       cluster.Services.Keepproxy.InternalURLs = map[arvados.URL]arvados.ServiceInstance{arvados.URL{Host: ":0"}: arvados.ServiceInstance{}}
+
        listener = nil
-       go main()
+       go func() {
+               run(log.New(), cluster)
+               defer closeListener()
+       }()
        waitForListener()
 
-       arv, err := arvadosclient.MakeArvadosClient()
+       client := arvados.NewClientFromEnv()
+       arv, err := arvadosclient.New(client)
        c.Assert(err, Equals, nil)
        if bogusClientToken {
                arv.ApiToken = "bogus-token"
@@ -119,7 +130,7 @@ func runProxy(c *C, args []string, bogusClientToken bool) *keepclient.KeepClient
 }
 
 func (s *ServerRequiredSuite) TestResponseViaHeader(c *C) {
-       runProxy(c, nil, false)
+       runProxy(c, false)
        defer closeListener()
 
        req, err := http.NewRequest("POST",
@@ -145,7 +156,7 @@ func (s *ServerRequiredSuite) TestResponseViaHeader(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestLoopDetection(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        sr := map[string]string{
@@ -163,7 +174,7 @@ func (s *ServerRequiredSuite) TestLoopDetection(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestStorageClassesHeader(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Set up fake keepstore to record request headers
@@ -190,7 +201,7 @@ func (s *ServerRequiredSuite) TestStorageClassesHeader(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestDesiredReplicas(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        content := []byte("TestDesiredReplicas")
@@ -207,7 +218,7 @@ func (s *ServerRequiredSuite) TestDesiredReplicas(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutWrongContentLength(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        content := []byte("TestPutWrongContentLength")
@@ -218,7 +229,7 @@ func (s *ServerRequiredSuite) TestPutWrongContentLength(c *C) {
        // fixes the invalid Content-Length header. In order to test
        // our server behavior, we have to call the handler directly
        // using an httptest.ResponseRecorder.
-       rtr := MakeRESTRouter(true, true, kc, 10*time.Second, "")
+       rtr := MakeRESTRouter(kc, 10*time.Second, "")
 
        type testcase struct {
                sendLength   string
@@ -246,7 +257,7 @@ func (s *ServerRequiredSuite) TestPutWrongContentLength(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestManyFailedPuts(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
        router.(*proxyHandler).timeout = time.Nanosecond
 
@@ -273,7 +284,7 @@ func (s *ServerRequiredSuite) TestManyFailedPuts(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
@@ -350,7 +361,7 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
-       kc := runProxy(c, nil, true)
+       kc := runProxy(c, true)
        defer closeListener()
 
        hash := fmt.Sprintf("%x+3", md5.Sum([]byte("bar")))
@@ -375,59 +386,8 @@ func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
 
 }
 
-func (s *ServerRequiredSuite) TestGetDisabled(c *C) {
-       kc := runProxy(c, []string{"-no-get"}, false)
-       defer closeListener()
-
-       hash := fmt.Sprintf("%x", md5.Sum([]byte("baz")))
-
-       {
-               _, _, err := kc.Ask(hash)
-               errNotFound, _ := err.(keepclient.ErrNotFound)
-               c.Check(errNotFound, NotNil)
-               c.Assert(err, ErrorMatches, `.*HTTP 405.*`)
-               c.Log("Ask 1")
-       }
-
-       {
-               hash2, rep, err := kc.PutB([]byte("baz"))
-               c.Check(hash2, Matches, fmt.Sprintf(`^%s\+3(\+.+)?$`, hash))
-               c.Check(rep, Equals, 2)
-               c.Check(err, Equals, nil)
-               c.Log("PutB")
-       }
-
-       {
-               blocklen, _, err := kc.Ask(hash)
-               errNotFound, _ := err.(keepclient.ErrNotFound)
-               c.Check(errNotFound, NotNil)
-               c.Assert(err, ErrorMatches, `.*HTTP 405.*`)
-               c.Check(blocklen, Equals, int64(0))
-               c.Log("Ask 2")
-       }
-
-       {
-               _, blocklen, _, err := kc.Get(hash)
-               errNotFound, _ := err.(keepclient.ErrNotFound)
-               c.Check(errNotFound, NotNil)
-               c.Assert(err, ErrorMatches, `.*HTTP 405.*`)
-               c.Check(blocklen, Equals, int64(0))
-               c.Log("Get")
-       }
-}
-
-func (s *ServerRequiredSuite) TestPutDisabled(c *C) {
-       kc := runProxy(c, []string{"-no-put"}, false)
-       defer closeListener()
-
-       hash2, rep, err := kc.PutB([]byte("quux"))
-       c.Check(hash2, Equals, "")
-       c.Check(rep, Equals, 0)
-       c.Check(err, FitsTypeOf, keepclient.InsufficientReplicasError(errors.New("")))
-}
-
 func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
-       runProxy(c, nil, false)
+       runProxy(c, false)
        defer closeListener()
 
        {
@@ -458,7 +418,7 @@ func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPostWithoutHash(c *C) {
-       runProxy(c, nil, false)
+       runProxy(c, false)
        defer closeListener()
 
        {
@@ -501,7 +461,7 @@ func (s *ServerRequiredSuite) TestStripHint(c *C) {
 //   With a valid but non-existing prefix (expect "\n")
 //   With an invalid prefix (expect error)
 func (s *ServerRequiredSuite) TestGetIndex(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Put "index-data" blocks
@@ -564,7 +524,7 @@ func (s *ServerRequiredSuite) TestGetIndex(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestCollectionSharingToken(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
        hash, _, err := kc.PutB([]byte("shareddata"))
        c.Check(err, IsNil)
@@ -577,7 +537,7 @@ func (s *ServerRequiredSuite) TestCollectionSharingToken(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutAskGetInvalidToken(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Put a test block
@@ -614,7 +574,7 @@ func (s *ServerRequiredSuite) TestPutAskGetInvalidToken(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestAskGetKeepProxyConnectionError(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Point keepproxy at a non-existent keepstore
@@ -640,7 +600,7 @@ func (s *ServerRequiredSuite) TestAskGetKeepProxyConnectionError(c *C) {
 }
 
 func (s *NoKeepServerSuite) TestAskGetNoKeepServerError(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
@@ -663,10 +623,10 @@ func (s *NoKeepServerSuite) TestAskGetNoKeepServerError(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPing(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
-       rtr := MakeRESTRouter(true, true, kc, 10*time.Second, arvadostest.ManagementToken)
+       rtr := MakeRESTRouter(kc, 10*time.Second, arvadostest.ManagementToken)
 
        req, err := http.NewRequest("GET",
                "http://"+listener.Addr().String()+"/_health/ping",
diff --git a/services/keepproxy/usage.go b/services/keepproxy/usage.go
deleted file mode 100644 (file)
index 6d3d21e..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package main
-
-import (
-       "encoding/json"
-       "flag"
-       "fmt"
-       "os"
-)
-
-func usage() {
-       c := DefaultConfig()
-       c.Client.APIHost = "zzzzz.arvadosapi.com:443"
-       exampleConfigFile, err := json.MarshalIndent(c, "    ", "  ")
-       if err != nil {
-               panic(err)
-       }
-       fmt.Fprintf(os.Stderr, `
-
-Keepproxy forwards GET and PUT requests to keepstore servers.  See
-http://doc.arvados.org/install/install-keepproxy.html
-
-Usage: keepproxy [-config path/to/keepproxy.yml]
-
-Options:
-`)
-       flag.PrintDefaults()
-       fmt.Fprintf(os.Stderr, `
-Example config file:
-    %s
-
-Client.APIHost:
-
-    Address (or address:port) of the Arvados API endpoint.
-
-Client.AuthToken:
-
-    Anonymous API token.
-
-Client.Insecure:
-
-    True if your Arvados API endpoint uses an unverifiable SSL/TLS
-    certificate.
-
-Listen:
-
-    Local port to listen on. Can be "address:port" or ":port", where
-    "address" is a host IP address or name and "port" is a port number
-    or name.
-
-DisableGet:
-
-    Respond 404 to GET and HEAD requests.
-
-DisablePut:
-
-    Respond 404 to PUT, POST, and OPTIONS requests.
-
-DefaultReplicas:
-
-    Default number of replicas to write if not specified by the
-    client. If this is zero or omitted, the site-wide
-    defaultCollectionReplication configuration will be used.
-
-Timeout:
-
-    Timeout for requests to keep services, with units (e.g., "120s",
-    "2m").
-
-PIDFile:
-
-    Path to PID file. During startup this file will be created if
-    needed, and locked using flock() until keepproxy exits. If it is
-    already locked, or any error is encountered while writing to it,
-    keepproxy will exit immediately. If omitted or empty, no PID file
-    will be used.
-
-Debug:
-
-    Enable debug logging.
-
-ManagementToken:
-
-    Authorization token to be included in all health check requests.
-
-`, exampleConfigFile)
-}
index 58bedd2841b41e0a5cf2a6e1a0cc0a8158ab69f0..951b592ea69d1893b6c030f539ce8e8d8bcf925e 100755 (executable)
@@ -79,6 +79,10 @@ Clusters:
         ExternalURL: "https://$localip:${services[workbench2-ssl]}"
       SSO:
         ExternalURL: "https://$localip:${services[sso]}"
+      Keepproxy:
+        InternalURLs:
+          "http://localhost:${services[keepproxy]}/": {}
+        ExternalURL: "http://$localip:${services[keepproxy-ssl]}/"
       Websocket:
         ExternalURL: "wss://$localip:${services[websockets-ssl]}/websocket"
         InternalURLs:
@@ -86,7 +90,9 @@ Clusters:
       GitSSH:
         ExternalURL: "ssh://git@$localip:"
       GitHTTP:
-        ExternalURL: "http://$localip:${services[arv-git-httpd]}/"
+        InternalURLs:
+          "http://localhost:${services[arv-git-httpd]}/": {}
+        ExternalURL: "https://$localip:${services[arv-git-httpd-ssl]}/"
       WebDAV:
         InternalURLs:
           "http://localhost:${services[keep-web]}/": {}
@@ -139,6 +145,10 @@ Clusters:
     Workbench:
       SecretKeyBase: $workbench_secret_key_base
       ArvadosDocsite: http://$localip:${services[doc]}/
+    Git:
+      GitCommand: /usr/share/gitolite3/gitolite-shell
+      GitoliteHome: /var/lib/arvados/git
+      Repositories: /var/lib/arvados/git/repositories
 EOF
 
 /usr/local/lib/arvbox/yml_override.py /var/lib/arvados/cluster_config.yml
index 21872749575cbcb2a4fb03ee7c97e0114046f56d..9d29eb9f143e8be2d9b88857e093524c73ee3c99 100644 (file)
@@ -33,6 +33,7 @@ services=(
   [controller-ssl]=8000
   [sso]=8900
   [composer]=4200
+  [arv-git-httpd-ssl]=9000
   [arv-git-httpd]=9001
   [keep-web]=9003
   [keep-web-ssl]=9002
index 9339f2328c6a9ee8a5e3058e537cb212ddbd0c00..38522a714c5b905563a6c35e0d855c5fda2d95ca 100755 (executable)
@@ -21,8 +21,4 @@ export ARVADOS_API_HOST_INSECURE=1
 export PATH="$PATH:/var/lib/arvados/git/bin"
 cd ~git
 
-exec /usr/local/bin/arv-git-httpd \
-    -address=:${services[arv-git-httpd]} \
-    -git-command=/usr/share/gitolite3/gitolite-shell \
-    -gitolite-home=/var/lib/arvados/git \
-    -repo-root=/var/lib/arvados/git/repositories
+exec /usr/local/bin/arv-git-httpd
index 78b5bcf8e8d430d302b4922c10b241c9469512f9..96457a6e5931a6dffa0378a09333b2c4edd53698 100755 (executable)
@@ -40,4 +40,4 @@ else
     echo $UUID > /var/lib/arvados/keepproxy-uuid
 fi
 
-exec /usr/local/bin/keepproxy -listen=:${services[keepproxy]}
+exec /usr/local/bin/keepproxy
index 04a1b539526f31547011d02d4db18ae508434883..0d60e74128365605a49194b27cb2cf9c09af9618 100755 (executable)
@@ -143,6 +143,29 @@ server {
     }
   }
 
+  upstream arvados-git-httpd {
+    server localhost:${services[arv-git-httpd]};
+  }
+  server {
+    listen *:${services[arv-git-httpd-ssl]} ssl default_server;
+    server_name arvados-git-httpd;
+    proxy_connect_timeout 90s;
+    proxy_read_timeout 300s;
+
+    ssl on;
+    ssl_certificate "${server_cert}";
+    ssl_certificate_key "${server_cert_key}";
+    client_max_body_size 50m;
+
+    location  / {
+      proxy_pass http://arvados-git-httpd;
+      proxy_set_header Host \$http_host;
+      proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+      proxy_set_header X-Forwarded-Proto https;
+      proxy_redirect off;
+    }
+  }
+
 }
 
 EOF
index cfcba1b21888a867698980a3f9434133d02ed607..c146a000325cb25b27cbe30e5e37c80b9144b0bf 100644 (file)
@@ -3,25 +3,25 @@
        "ignore": "test",
        "package": [
                {
-                       "checksumSHA1": "j4je0EzPGzjb6INLY1BHZ+hyMjc=",
+                       "checksumSHA1": "jfYWZyRWLMfG0J5K7G2K8a9AKfs=",
                        "origin": "github.com/curoverse/goamz/aws",
                        "path": "github.com/AdRoll/goamz/aws",
-                       "revision": "888b4804f2653cd35ebcc95f046079e63b5b2799",
-                       "revisionTime": "2017-07-27T13:52:37Z"
+                       "revision": "1bba09f407ef1d02c90bc37eff7e91e2231fa587",
+                       "revisionTime": "2019-09-05T14:15:25Z"
                },
                {
-                       "checksumSHA1": "0+n3cT6e7sQCCbBAH8zg6neiHTk=",
+                       "checksumSHA1": "lqoARtBgwnvhEhLyIjR3GLnR5/c=",
                        "origin": "github.com/curoverse/goamz/s3",
                        "path": "github.com/AdRoll/goamz/s3",
-                       "revision": "888b4804f2653cd35ebcc95f046079e63b5b2799",
-                       "revisionTime": "2017-07-27T13:52:37Z"
+                       "revision": "1bba09f407ef1d02c90bc37eff7e91e2231fa587",
+                       "revisionTime": "2019-09-05T14:15:25Z"
                },
                {
                        "checksumSHA1": "tvxbsTkdjB0C/uxEglqD6JfVnMg=",
                        "origin": "github.com/curoverse/goamz/s3/s3test",
                        "path": "github.com/AdRoll/goamz/s3/s3test",
-                       "revision": "888b4804f2653cd35ebcc95f046079e63b5b2799",
-                       "revisionTime": "2017-07-27T13:52:37Z"
+                       "revision": "1bba09f407ef1d02c90bc37eff7e91e2231fa587",
+                       "revisionTime": "2019-09-05T14:15:25Z"
                },
                {
                        "checksumSHA1": "KF4DsRUpZ+h+qRQ/umRAQZfVvw0=",